Java服務(wù)器宕機的解決方法論
1 宕機概要
1.1 定義
向服務(wù)器的請求都沒有響應(yīng)或者響應(yīng)非常慢。
前端界面的崩潰并非宕機。
1.2 分類
進程閃退
- 內(nèi)部崩潰
- 外部終止
線程鎖死或者無限等待
內(nèi)存溢出
下面分別進行詳解
2 進程閃退
2.1 內(nèi)部崩潰
JVM 發(fā)生內(nèi)部崩潰,必然會生成"hs_err_pid"開頭的文件。
下面講一種常見情況:
無法申請內(nèi)存,顯示commit_memory錯誤
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 設(shè)置過大,超過系統(tǒng)可用內(nèi)存,JVM 申請內(nèi)存失敗。
比如服務(wù)器總內(nèi)存32G ,同時運行多個程序,程序 A 配了20GXmx,其他程序也配了20G Xmx ,Linux的交換空間也沒有設(shè)置,這時候如果其他程序用滿20G內(nèi)存那么服務(wù)的可用內(nèi)存必然低于12G,這時如果Tomcat需要大于12G的內(nèi)存就很容易發(fā)生該錯誤,直接宕機!
解決方案
- 減少Xmx值使得所有的綜合不超過服務(wù)器物理內(nèi)存
- 調(diào)整 Xms=Xmx
- 服務(wù)器不要運行其他不必要的東西
- 配置一部分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或者對應(yīng)日期文件,看看有沒有類似下面的內(nèi)容,日志有時間可以判斷
2.2.2 SSH注銷
檢查/var/log/auth.log,/var/log/secure或者對應(yīng)日期的文件,檢查宕機的時間點有沒有
時間吻合,那么宕機原因即可確認。
解決方案
使用nohup命令在后臺運行啟動程序,檢查ssh注銷原因
2.2.3 其他人為因素
不是很好判斷,需要給shell加上操作記錄
3 線程鎖死/無限等待
表現(xiàn)
系統(tǒng)無法訪問時,當前cpu占用非常低
使用 jstack命令輸出線程堆棧即可
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個核心對應(yīng)一個gc線程
大于8
- gc的線程數(shù)= 8 + ((N - 8) * 5/8)
N代表核心的數(shù)量,這是默認的gc線程創(chuàng)建公式
threads = N <= 8 ? N : (8 + ((N - 8) * 5/8))
當然也可以通過參數(shù) -XX:ParallelCMSThreads=20 來配置 GC 線程數(shù),就不會使用默認的設(shè)置,默認情況下不要調(diào)整,因為調(diào)了也沒什么卵用,最多在宕機的時候cpu占用按照你設(shè)定的值來。
當發(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í)行,這就導(dǎo)致了cpu全部占滿的現(xiàn)象,所以說內(nèi)存溢出的時候,一定伴隨cpu占滿(按照設(shè)置或者公式計算的線程量)
4.2 JVM內(nèi)存分配機制
在說說JVM怎么分配內(nèi)存的,大家都知道給客戶配置Xmx參數(shù)和xms參數(shù),Xmx代表的是最大堆內(nèi)存,xms代表的是最小堆內(nèi)存,至于permsize就和這些都沒有關(guān)系,不能算在內(nèi)存溢出,遇到拋錯outofmemory permsize什么的調(diào)大就行了
permsize是一個被jvm也拋棄的參數(shù)只存在1.7之前的jdk中,是用來保存java的class等內(nèi)容的存儲空間,1.8被metaspace替代
這個內(nèi)存怎么不回收的啊,一問都是在任務(wù)管理器看的!這個地方是看不到內(nèi)存回收的,或者說他也會回收,但是可能要等個好幾天才會回收一次,可以忽略這種機制的存在
形而上學
WC 論
如果把內(nèi)存比喻成茅坑,操作系統(tǒng)64g內(nèi)存就是一共64個茅坑,那么JVM的內(nèi)存回收相當于茅坑調(diào)度系統(tǒng),每個gc線程相當于調(diào)度系統(tǒng)派出去的茅坑檢查員,給jvm設(shè)置了 Xms=2g, Xmx=32g,那么程序啟動,jvm直接占了兩個茅坑,任務(wù)管理器看到內(nèi)存占用2g,即使沒人上廁所,JVM也不會把坑還給操作系統(tǒng)。
假設(shè)一個人上廁所10秒,一開始的時候 20秒有一個人來上廁所,那么 jvm通過茅坑檢查員發(fā)現(xiàn)哎兩個坑總有一個是空的,維持茅坑數(shù)量不變,內(nèi)存的占用一直是2g,過了些時候,來的人開始增多了,變成5秒有一個人來上廁所,茅坑檢查員向JVM匯報有人開始有排隊了,兩個坑位很緊張,不行要多弄幾個坑才行,于是,jvm向系統(tǒng)又申請了兩個坑,任務(wù)管理器可以看到內(nèi)存占用變成了4個G,這時候又突然發(fā)生壓力增大,變成了1秒來一個人,4個坑肯定不夠啊,于是jvm又把內(nèi)存擴容到10-11g,現(xiàn)在夠用了,任務(wù)管理器會看到內(nèi)存一直維持在10-11g,終于大家都上完廁所了,沒人排隊了,茅坑都空出來了。
但是,jvm是個霸道總裁,被他占的東西,除非死不然不會吐出來的,所以任務(wù)管理器里面看到內(nèi)存還是10-11g不會降低,除非jvm死了,實際沒有任何內(nèi)存占用(所以不要再說內(nèi)存不回收的問題,這個內(nèi)存的回收不回收和宕機是沒有直接關(guān)系的)
如果這時候突然一下子來了很多很多人,比如一下子來了64個人要上廁所,這時候會怎樣了,JVM把他的所有的茅坑檢查員都派出去檢查啊,然后發(fā)現(xiàn)完蛋了茅坑不夠用啊,申請到32個都不夠用啊,于是jvm的特派茅坑檢查員就一個坑一個坑的拍,一個坑一個坑的催,結(jié)果呢,檢查員在催,大家就拉不出來了,上廁所的時間無限期延長,外面的人要進去,里面的人出不來,BOOM,廁所就不響應(yīng)了,后面來的人都拉褲子了。
怎么解決?
- 換個茅坑管理員,更好的調(diào)度茅坑檢查員和分配茅坑,這就有了G1回收器 ,茅坑越多效果越好,目前JDK情況內(nèi)存大于10G的情況G1的效果好于CMS,低于10G的情況下不如CMS
- 從源頭控制人員,不要一下子來這么多人(申請內(nèi)存),也就是常見的不要讓業(yè)務(wù)查大量數(shù)據(jù)占內(nèi)存。
而上面講的線程鎖死的情況要做類比的話,就是32個坑唄32個人占了,還死活不肯出來,導(dǎo)致后面排隊的人失去響應(yīng)了。
沒有味道的比喻
解釋一下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)品,比如大技術(shù)支持大團隊引用,大團隊又被公司引用,最終公司這個大對象占用了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 總結(jié)
宕機分析的目的就是要找到占用內(nèi)存的東西,把他找出來,找出他的原因,然后把它改掉。JVM的內(nèi)存對象分配相當于一顆樹,所有的對象都被層層引用,直到GCRoot根節(jié)點,如果沒有根節(jié)點的引用,這個對象是完全可以直接釋放掉的,大部分也是因為gcRoot存在的對象過多導(dǎo)致的宕機,當然也不排除可以使用已經(jīng)回收的對象來分析,由于生成dump的時間不精確,可能他生成的時候 ,對應(yīng)的大組件已經(jīng)回收了,但是jvm緩過來還需要一些時間,所以還是處于大量gc的狀態(tài),這時候只能通過對于引用的檢索找到最多的引用對象來進行分析。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實現(xiàn)截取PDF指定頁并進行圖片格式轉(zhuǎn)換功能
這篇文章主要介紹了java實現(xiàn)截取PDF指定頁并進行圖片格式轉(zhuǎn)換功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09通過Java?Reflection實現(xiàn)編譯時注解正確處理方法
Java注解是一種標記在JDK5及以后的版本中引入,用于Java語言中向程序添加元數(shù)據(jù)的方法,這篇文章主要介紹了通過Java?Reflection實現(xiàn)編譯時注解處理方法,需要的朋友可以參考下2023-06-06PowerJob的ServerDiscoveryService工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的ServerDiscoveryService工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12Elasticsearch算分優(yōu)化方案之rescore_query示例詳解
這篇文章主要為大家介紹了Elasticsearch算分優(yōu)化方案之rescore_query示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08使用Java對數(shù)據(jù)庫進行基本的查詢和更新操作
這篇文章主要介紹了使用Java對數(shù)據(jù)庫進行基本的查詢和更新操作,是Java入門學習中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10Spring Data JPA+kkpager實現(xiàn)分頁功能實例
本篇文章主要介紹了Spring Data JPA+kkpager實現(xiàn)分頁功能實例,具有一定的參考價值,有興趣的可以了解一下2017-06-06SpringBoot整合redis+Aop防止重復(fù)提交的實現(xiàn)
Spring Boot通過AOP可以實現(xiàn)防止表單重復(fù)提交,本文主要介紹了SpringBoot整合redis+Aop防止重復(fù)提交的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07SpringMVC 通過commons-fileupload實現(xiàn)文件上傳功能
這篇文章主要介紹了SpringMVC 通過commons-fileupload實現(xiàn)文件上傳,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02