1 宕機概要
1.1 定義
向服務器的請求都沒有響應或者響應非常慢。
前端界面的崩潰并非宕機。
1.2 分類
進程閃退
- 內(nèi)部崩潰
- 外部終止
線程鎖死或者無限等待
內(nèi)存溢出
下面分別進行詳解
2 進程閃退
2.1 內(nèi)部崩潰
JVM 發(fā)生內(nèi)部崩潰,必然會生成"hs_err_pid"開頭的文件。
下面講一種常見情況:
無法申請內(nèi)存,顯示commit_memory錯誤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Current thread ( 0x00007f3e40013000 ): JavaThread "Unknown thread" [_thread_in_vm, id= 11408 , stack( 0x00007f3e49983000 , 0x00007f3e49a84000 )] Stack: [ 0x00007f3e49983000 , 0x00007f3e49a84000 ], sp= 0x00007f3e49a82360 , free space=1020k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C= native code) V [libjvm.so+ 0x9a32da ] VMError::report_and_die()+ 0x2ea V [libjvm.so+ 0x497f7b ] report_vm_out_of_memory( char const *, int , unsigned long , char const *)+ 0x9b V [libjvm.so+ 0x81fcce ] os::Linux::commit_memory_impl( char *, unsigned long , bool)+ 0xfe V [libjvm.so+ 0x820219 ] os::pd_commit_memory( char *, unsigned long , unsigned long , bool)+ 0x29 V [libjvm.so+ 0x819faa ] os::commit_memory( char *, unsigned long , unsigned long , bool)+ 0x2a V [libjvm.so+ 0x99eae9 ] VirtualSpace::expand_by(unsigned long , bool)+ 0x1c9 V [libjvm.so+ 0x99ec6d ] VirtualSpace::initialize(ReservedSpace, unsigned long )+ 0xcd V [libjvm.so+ 0x57962f ] CardGeneration::CardGeneration(ReservedSpace, unsigned long , int , GenRemSet*)+ 0x11f V [libjvm.so+ 0x46ceed ] ConcurrentMarkSweepGeneration::ConcurrentMarkSweepGeneration(ReservedSpace, unsigned long , int , CardTableRS*, bool, FreeBlockDictionary<FreeChunk>::DictionaryChoice)+ 0x5d V [libjvm.so+ 0x57a906 ] GenerationSpec::init(ReservedSpace, int , GenRemSet*)+ 0x106 V [libjvm.so+ 0x56afe4 ] GenCollectedHeap::initialize()+ 0x344 V [libjvm.so+ 0x9751aa ] Universe::initialize_heap()+ 0xca V [libjvm.so+ 0x976379 ] universe_init()+ 0x79 V [libjvm.so+ 0x5b1d25 ] init_globals()+ 0x65 V [libjvm.so+ 0x95dc6d ] Threads::create_vm(JavaVMInitArgs*, bool*)+ 0x1ed V [libjvm.so+ 0x639fe4 ] JNI_CreateJavaVM+ 0x74 |
這一般是因為 Xmx 設置過大,超過系統(tǒng)可用內(nèi)存,JVM 申請內(nèi)存失敗。
比如服務器總內(nèi)存32G ,同時運行多個程序,程序 A 配了20GXmx,其他程序也配了20G Xmx ,Linux的交換空間也沒有設置,這時候如果其他程序用滿20G內(nèi)存那么服務的可用內(nèi)存必然低于12G,這時如果Tomcat需要大于12G的內(nèi)存就很容易發(fā)生該錯誤,直接宕機!
解決方案
- 減少Xmx值使得所有的綜合不超過服務器物理內(nèi)存
- 調(diào)整 Xms=Xmx
- 服務器不要運行其他不必要的東西
- 配置一部分swap空間(虛擬內(nèi)存)
2.2 外部終止
如果找不到"hs_err_pid"開頭的文件,那么這個進程的閃退必然是被從外部終止的。
2.2.1 OOMKiller
java長期內(nèi)存占用過高,系統(tǒng)需要內(nèi)存使用的時候沒有內(nèi)存,Linux的oomkiller機制會干掉最低優(yōu)先級的內(nèi)存
檢查 /var/logs/message , /var/logs/dmesg或者對應日期文件,看看有沒有類似下面的內(nèi)容,日志有時間可以判斷
2.2.2 SSH注銷
檢查/var/log/auth.log,/var/log/secure或者對應日期的文件,檢查宕機的時間點有沒有
時間吻合,那么宕機原因即可確認。
解決方案
使用nohup命令在后臺運行啟動程序,檢查ssh注銷原因
2.2.3 其他人為因素
不是很好判斷,需要給shell加上操作記錄
3 線程鎖死/無限等待
表現(xiàn)
系統(tǒng)無法訪問時,當前cpu占用非常低
使用 jstack命令輸出線程堆棧即可
1
2
3
|
jstack pid >> 1 .txt or jstack -F pid >> 1 .txt |
都行,或者用jprofiler工具看堆棧,或者其他任何可以拿到堆棧的工具都可以, java的堆棧就是java方法調(diào)用的路徑,可以定位一些簡單的問題
4 內(nèi)存溢出
現(xiàn)象
CPU全部占滿,內(nèi)存達到配置Xmx最大值
4.1 CPU占滿緣由
并不是 CPU 不夠用,而是涉及到JVM的GC 機制,大部分情況來說CPU都是過剩的
JVM 使用GC的方法來回收沒有被引用的內(nèi)存塊,在當前的回收機制中,回收器是并發(fā)進行的,回收的線程個數(shù)有一個公式:
當CPU核心數(shù)
小于8
- 1個核心對應一個gc線程
大于8
- gc的線程數(shù)= 8 + ((N - 8) * 5/8)
N代表核心的數(shù)量,這是默認的gc線程創(chuàng)建公式
1
|
threads = N <= 8 ? N : ( 8 + ((N - 8 ) * 5 / 8 )) |
當然也可以通過參數(shù) -XX:ParallelCMSThreads=20 來配置 GC 線程數(shù),就不會使用默認的設置,默認情況下不要調(diào)整,因為調(diào)了也沒什么卵用,最多在宕機的時候cpu占用按照你設定的值來。
當發(fā)生內(nèi)存溢出的時候,或者快要內(nèi)存溢出的時候,不一定是內(nèi)存溢出,JVM 發(fā)現(xiàn)內(nèi)存不夠了,就會 GC,所有線程開始工作,暫停 JVM 運行,開始回收,如果回收到內(nèi)存了,ok,jvm可以正確繼續(xù)執(zhí)行,
這也就是為什么有時候配置內(nèi)存溢出的參數(shù)沒有自動生成dump的原因,因為他能運行,但是比較慢,所以沒有OOM,就不會生成dump,
如果沒有回收到什么內(nèi)存,gc會循環(huán)持續(xù)執(zhí)行,這就導致了cpu全部占滿的現(xiàn)象,所以說內(nèi)存溢出的時候,一定伴隨cpu占滿(按照設置或者公式計算的線程量)
4.2 JVM內(nèi)存分配機制
在說說JVM怎么分配內(nèi)存的,大家都知道給客戶配置Xmx參數(shù)和xms參數(shù),Xmx代表的是最大堆內(nèi)存,xms代表的是最小堆內(nèi)存,至于permsize就和這些都沒有關系,不能算在內(nèi)存溢出,遇到拋錯outofmemory permsize什么的調(diào)大就行了
permsize是一個被jvm也拋棄的參數(shù)只存在1.7之前的jdk中,是用來保存java的class等內(nèi)容的存儲空間,1.8被metaspace替代
這個內(nèi)存怎么不回收的啊,一問都是在任務管理器看的!這個地方是看不到內(nèi)存回收的,或者說他也會回收,但是可能要等個好幾天才會回收一次,可以忽略這種機制的存在
形而上學
WC 論
如果把內(nèi)存比喻成茅坑,操作系統(tǒng)64g內(nèi)存就是一共64個茅坑,那么JVM的內(nèi)存回收相當于茅坑調(diào)度系統(tǒng),每個gc線程相當于調(diào)度系統(tǒng)派出去的茅坑檢查員,給jvm設置了 Xms=2g, Xmx=32g,那么程序啟動,jvm直接占了兩個茅坑,任務管理器看到內(nèi)存占用2g,即使沒人上廁所,JVM也不會把坑還給操作系統(tǒng)。
假設一個人上廁所10秒,一開始的時候 20秒有一個人來上廁所,那么 jvm通過茅坑檢查員發(fā)現(xiàn)哎兩個坑總有一個是空的,維持茅坑數(shù)量不變,內(nèi)存的占用一直是2g,過了些時候,來的人開始增多了,變成5秒有一個人來上廁所,茅坑檢查員向JVM匯報有人開始有排隊了,兩個坑位很緊張,不行要多弄幾個坑才行,于是,jvm向系統(tǒng)又申請了兩個坑,任務管理器可以看到內(nèi)存占用變成了4個G,這時候又突然發(fā)生壓力增大,變成了1秒來一個人,4個坑肯定不夠啊,于是jvm又把內(nèi)存擴容到10-11g,現(xiàn)在夠用了,任務管理器會看到內(nèi)存一直維持在10-11g,終于大家都上完廁所了,沒人排隊了,茅坑都空出來了。
但是,jvm是個霸道總裁,被他占的東西,除非死不然不會吐出來的,所以任務管理器里面看到內(nèi)存還是10-11g不會降低,除非jvm死了,實際沒有任何內(nèi)存占用(所以不要再說內(nèi)存不回收的問題,這個內(nèi)存的回收不回收和宕機是沒有直接關系的)
如果這時候突然一下子來了很多很多人,比如一下子來了64個人要上廁所,這時候會怎樣了,JVM把他的所有的茅坑檢查員都派出去檢查啊,然后發(fā)現(xiàn)完蛋了茅坑不夠用啊,申請到32個都不夠用啊,于是jvm的特派茅坑檢查員就一個坑一個坑的拍,一個坑一個坑的催,結果呢,檢查員在催,大家就拉不出來了,上廁所的時間無限期延長,外面的人要進去,里面的人出不來,BOOM,廁所就不響應了,后面來的人都拉褲子了。
怎么解決?
- 換個茅坑管理員,更好的調(diào)度茅坑檢查員和分配茅坑,這就有了G1回收器 ,茅坑越多效果越好,目前JDK情況內(nèi)存大于10G的情況G1的效果好于CMS,低于10G的情況下不如CMS
- 從源頭控制人員,不要一下子來這么多人(申請內(nèi)存),也就是常見的不要讓業(yè)務查大量數(shù)據(jù)占內(nèi)存。
而上面講的線程鎖死的情況要做類比的話,就是32個坑唄32個人占了,還死活不肯出來,導致后面排隊的人失去響應了。
沒有味道的比喻
解釋一下java的面向?qū)ο蠛蛯ο笠?
一棟大樓,10層共1000個工位 (類比物理內(nèi)存)。
包給一個二房東 中介公司Z (jvm)。
中介公司和大樓物業(yè)談好彈性繳費,租多少出去收多少錢。
Z公司先一下租300個位置 (類比Xms)省錢,
Z公司和物業(yè)談好最多租600個位置(類比Xmx)。
Z公司找到了公司A(200人)來這里 就占用了200個工位 (類比一次數(shù)據(jù)查詢)。
公司A是一個大的對象,每個人類比最小的單元格,每個小團隊也是一個對象,個人被小團隊引用,小團隊又被更上級的比如產(chǎn)品,比如大技術支持大團隊引用,大團隊又被公司引用,最終公司這個大對象占用了200工位,類比下來200個工位內(nèi)存不釋放的根就是這個公司在這兒上班。
這時候公司A倒閉了,200個工位就空出來了(內(nèi)存釋放)。
- 內(nèi)存溢出宕機是什么情況呢?
- 找Z公司租工位的公司,總工位超過了600,總不能坐大腿上上班啊,于是物業(yè)不會給Z工位的,合同寫的好好的,Z公司不滿足客戶需求,運作不起來破產(chǎn)倒閉。
- 經(jīng)常遇到的申請內(nèi)存失敗的崩潰是什么情況?
- 物業(yè)是個滑頭,不止找了Z公司一家中介,還有Y公司也是做中介的(類比兩個JVM)。都承諾Z和Y公司都是最多可以租600個位置。初始都租的300個位置,大家相處融洽,隨著公司不停入住,矛盾出現(xiàn)了:
- Y公司效益比較好,先找了公司,已經(jīng)占了600位置;
- 這時候Z公司的效益也上來了,也要增加工位 (類比申請內(nèi)存),這時候物業(yè)根本沒有位置能給他。于是Z公司運轉(zhuǎn)不下去,破產(chǎn)倒閉
5 總結
宕機分析的目的就是要找到占用內(nèi)存的東西,把他找出來,找出他的原因,然后把它改掉。JVM的內(nèi)存對象分配相當于一顆樹,所有的對象都被層層引用,直到GCRoot根節(jié)點,如果沒有根節(jié)點的引用,這個對象是完全可以直接釋放掉的,大部分也是因為gcRoot存在的對象過多導致的宕機,當然也不排除可以使用已經(jīng)回收的對象來分析,由于生成dump的時間不精確,可能他生成的時候 ,對應的大組件已經(jīng)回收了,但是jvm緩過來還需要一些時間,所以還是處于大量gc的狀態(tài),這時候只能通過對于引用的檢索找到最多的引用對象來進行分析。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://javaedge.blog.csdn.net/article/details/104567442