一次非常典型的 JVM OOM 事故
當(dāng)面對(duì) JVM OOM 時(shí),你會(huì)緊張嗎 ? 會(huì)不會(huì)手足無(wú)措 ?
這篇文章,分享前段時(shí)間幫一位同學(xué)梳理面對(duì) JVM OOM 事故時(shí)的解題思路。
圖片
首先從對(duì)話中,我們可以看到內(nèi)存溢出呈現(xiàn)兩種情況:
- 運(yùn)行一段時(shí)間之后,CPU 飆高 ;
- 服務(wù)假死,表現(xiàn)出來(lái)日志沒有任何輸出。
我的第一反應(yīng)是:非常明顯的 JVM 內(nèi)存溢出表現(xiàn) ,不過(guò)不知道是爆炸性的內(nèi)存增長(zhǎng),還是緩慢的內(nèi)存增長(zhǎng)。
于是,我回復(fù):可以每隔一段時(shí)間 觀察 top -p Pid (進(jìn)程號(hào)) 看看應(yīng)用的內(nèi)存占用情況。
類似的效果見下圖:
圖片
接下來(lái),我讓他通過(guò) jstat -gcutil pid 1000 看看 gc 的頻率 。
圖片
從圖中,新生代 E 區(qū)和老年代基本都滿了 ,我基本可以確定是海量大對(duì)象產(chǎn)生導(dǎo)致 JVM OOM 了。
圖片
定時(shí)任務(wù)這四個(gè)字如電光火石般在我眼前閃過(guò),基本八九不離十了。
圖片
接下來(lái),他發(fā)了張那段時(shí)間的監(jiān)控圖:
圖片
哇,這張圖太有畫面感了,我都能感覺到 GC 線程在四處滅火,但依然無(wú)法釋放內(nèi)存的彷徨。
最后,我有點(diǎn)擔(dān)心,是不是 JVM 內(nèi)存分配小了才導(dǎo)致 OOM 了,同學(xué)的回復(fù)是 : 12 G 。
我覺得內(nèi)存大小還可以 ,一般情況下通過(guò) jmap -heap pid 來(lái)查看,示例圖如下:
圖片
分析到這里,基本上我得到了如下的結(jié)論:
1、要查看代碼中是否有一次性查詢海量對(duì)象的操作 ;
2、或者有什么公共的對(duì)象一直在使用,而忘記了釋放;
3、12 G 對(duì)一般的小應(yīng)用來(lái)講是綽綽有余的,而且他們的應(yīng)用非高并發(fā)場(chǎng)景,是內(nèi)網(wǎng)系統(tǒng)。
圖片
最后,我建議觀察在日志停的那個(gè)時(shí)刻到底做了哪些事情,那才是真正的案發(fā)現(xiàn)場(chǎng)。
那到底是什么原因?qū)е?JVM OOM 呢 ? 和我預(yù)期的基本一模一樣:
圖片
SQL 語(yǔ)句類似下圖,查詢條件沒有拼接好,導(dǎo)致全表掃描。
圖片
我們總結(jié)下,解決 JVM 內(nèi)存溢出問(wèn)題的流程:
1、分析事故現(xiàn)場(chǎng)(CPU、內(nèi)存、日志);
2、通過(guò) top -p Pid (進(jìn)程號(hào))分析進(jìn)程資源占用,判斷是爆炸性的內(nèi)存增長(zhǎng),還是緩慢的內(nèi)存增長(zhǎng)。
3、 jstat -gcutil pid 1000 看看 gc 的頻率 ,可以分析是否有大對(duì)象產(chǎn)生以及 查看 GC 頻率。
4、 jmap -heap pid 分析真實(shí)的 JVM 內(nèi)存占用 ,確認(rèn)是否真的內(nèi)存分配得太小了。
5、 事故發(fā)生當(dāng)時(shí)到底做了什么,有沒有出現(xiàn)類似于內(nèi)存或者 CPU 占用呈現(xiàn)脈沖飆高樣子。
6、 若有飆高的場(chǎng)景,分析彼時(shí)彼刻到底有哪些操作。
7、 若是緩慢增長(zhǎng),則考慮使用 MAT 結(jié)合排除法分析內(nèi)存占用。
上面的流程是我解決過(guò)內(nèi)存溢出的套路,雖然很糙,但很實(shí)用,比如曾經(jīng)幫助藝龍支付團(tuán)隊(duì)解決過(guò)訂單查詢內(nèi)存溢出問(wèn)題、西南某航空公司用戶中心內(nèi)存溢出問(wèn)題等等。
最后,我想說(shuō):一定要注意 where 1 = 1 哦 ,真的出現(xiàn)太多次啦。