java進(jìn)程的內(nèi)存分析 OOM
java項(xiàng)目內(nèi)存溢出(OOM)的排查方法及原因分析
線上的項(xiàng)目崩了,第一步就是重啟服務(wù)圖片,然后排查下項(xiàng)目日志,沒發(fā)現(xiàn)異常,繼續(xù)排查代碼,也沒找到問題所在,我們真的是太菜了圖片,
重啟后,幾分鐘有崩了,瑟瑟發(fā)抖圖片。
然后開始進(jìn)程信息排查
先top命令查看了下資源消耗情況,發(fā)現(xiàn)異常的java服務(wù)cpu使用率達(dá)200%,內(nèi)存占用也極高,所以又通過jstat -gc xx命令查看了下java堆的狀況
主要觀察的是如下幾個指標(biāo)
YGC:新生代垃圾回收次數(shù)
YGCT:新生代垃圾回收消耗時間
FGC:老年代垃圾回收次數(shù)
FGCT:老年代垃圾回收消耗時間
GCT:垃圾回收消耗總時間
young gc次數(shù)多很正常,gc線程耗時很短,且不會影響其他線程。
而full gc次數(shù)也很多且耗時較長就很奇怪了,因?yàn)槔夏甏惶赡芤幌戮陀羞@么多內(nèi)存占用
這才重啟多久啊?。?!
況且full gc的時候,會發(fā)生stop-the-world現(xiàn)象。即除了gc線程外,其他線程都會被暫停,正好和服務(wù)進(jìn)程還在,但info日志不再輸出的現(xiàn)象對應(yīng)?。?!
隨即通過jmap -dump:format=b,file=dump.txt 進(jìn)程號 命令,保存了當(dāng)時的堆快照文件,然后準(zhǔn)備進(jìn)行分析,看看老年代究竟放了些什么對象。
命令:
jmap -dump:format=b,file=dump.hprof 1273315
然后就會保存 進(jìn)程號 = 1273315 的程序的堆快照文件
然后把這個文件下載下來,用 MemoryAnalyzer 工具進(jìn)行分析
如何使用MemoryAnalyzer內(nèi)存分析工具
下載的地址連接在
鏈接:https://pan.baidu.com/s/1Pi48eZhdEC7rNCKs9QDO_w
提取碼:y91z
界面如下
然后內(nèi)存分析界面如下
詳細(xì)使用方法完畢
下面是上述提供的生產(chǎn)級別的排除分析圖
點(diǎn)擊紅框處可查看調(diào)用方法棧
在當(dāng)時的分析結(jié)果里,圖1中堆占用率前三的點(diǎn)擊“see stacktrace”都指向同一處業(yè)務(wù)代碼:
調(diào)用了mybatis mapper去select,說明有三個線程運(yùn)行到那處代碼時都產(chǎn)生了大對象,直接耗盡了內(nèi)存,持續(xù)引發(fā)full gc。
后面分析了那段代碼邏輯,是根據(jù)用戶的某個查詢參數(shù)去關(guān)聯(lián)表查出商編號,再去商戶表里查出對應(yīng)商戶,再遍歷組裝下數(shù)據(jù),再返回給前端。
但是寫這段代碼的同事,直接把關(guān)聯(lián)表查出的商編號作為參數(shù)去商戶表查,沒有判空,而查商戶表時對商編號有如下判斷
所以導(dǎo)致如果關(guān)聯(lián)表查出數(shù)據(jù)為空,這里不會根據(jù)customer_no去查,基本等同于全表查詢,而商戶表有300多萬條數(shù)據(jù),一次查詢會返回一個300多萬條數(shù)據(jù)的list!??!
事故原因復(fù)盤
當(dāng)?shù)谝淮斡|發(fā)bug時,jvm會把產(chǎn)生的大對象A1分配在新生代(如果此時新生代gc后內(nèi)存足夠的話)。
第二次觸發(fā)時,新生代gc后內(nèi)存可能不夠放大對象A2了(比如第一次的請求還未結(jié)束,A1沒有被回收),jvm會直接把A2放在老年代。
第三,第四次觸發(fā)時,產(chǎn)生的大對象A3,A4也放在了老年代。
可能第五次觸發(fā)時,老年代內(nèi)存也不夠了,就會觸發(fā)full gc,一次full gc釋放的內(nèi)存不夠,就會繼續(xù)full gc。
而full gc時,之前還未處理完的請求線程被暫停,大對象還在被引用,無法回收,就會像類似死循環(huán)一樣,一直full gc了
你學(xué)廢了。。。。。。
作者:老王
歡迎關(guān)注微信公眾號 : IT學(xué)習(xí)道場