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í)道場