Java服務(wù),內(nèi)存OOM問(wèn)題如何快速定位?
最近有朋友在知識(shí)星球提問(wèn):
沈老師,有一個(gè)Java服務(wù)出現(xiàn)了OOM(Out Of Memory)問(wèn)題,定位了好久不得其法,請(qǐng)問(wèn)有什么好的思路么?
OOM的問(wèn)題,印象中之前寫過(guò),這里再總結(jié)一些相對(duì)通用的方案,希望能幫助到Java技術(shù)棧的同學(xué)。
某Java服務(wù)(假設(shè)PID=10765)出現(xiàn)了OOM,最常見(jiàn)的原因?yàn)椋?/p>
- 有可能是內(nèi)存分配確實(shí)過(guò)小,而正常業(yè)務(wù)使用了大量?jī)?nèi)存
 - 某一個(gè)對(duì)象被頻繁申請(qǐng),卻沒(méi)有釋放,內(nèi)存不斷泄漏,導(dǎo)致內(nèi)存耗盡
 - 某一個(gè)資源被頻繁申請(qǐng),系統(tǒng)資源耗盡,例如:不斷創(chuàng)建線程,不斷發(fā)起網(wǎng)絡(luò)連接
 
畫外音:無(wú)非“本身資源不夠”“申請(qǐng)資源太多”“資源耗盡”幾個(gè)原因。
更具體的,可以使用以下工具逐一排查。
一、確認(rèn)是不是內(nèi)存本身就分配過(guò)小
方法:
- jmap -heap 10765
 
如上圖,可以查看新生代,老生代堆內(nèi)存的分配大小以及使用情況,看是否本身分配過(guò)小。
二、找到最耗內(nèi)存的對(duì)象
方法:
- jmap -histo:live 10765 | more
 
如上圖,輸入命令后,會(huì)以表格的形式顯示存活對(duì)象的信息,并按照所占內(nèi)存大小排序:
- 實(shí)例數(shù)
 - 所占內(nèi)存大小
 - 類名
 
是不是很直觀?對(duì)于實(shí)例數(shù)較多,占用內(nèi)存大小較多的實(shí)例/類,相關(guān)的代碼就要針對(duì)性review了。
上圖中占內(nèi)存最多的對(duì)象是RingBufferLogEvent,共占用內(nèi)存18M,屬于正常使用范圍。
如果發(fā)現(xiàn)某類對(duì)象占用內(nèi)存很大(例如幾個(gè)G),很可能是類對(duì)象創(chuàng)建太多,且一直未釋放。例如:
- 申請(qǐng)完資源后,未調(diào)用close()或dispose()釋放資源
 - 消費(fèi)者消費(fèi)速度慢(或停止消費(fèi)了),而生產(chǎn)者不斷往隊(duì)列中投遞任務(wù),導(dǎo)致隊(duì)列中任務(wù)累積過(guò)多
 
畫外音:線上執(zhí)行該命令會(huì)強(qiáng)制執(zhí)行一次fgc。另外還可以dump內(nèi)存進(jìn)行分析。
三、確認(rèn)是否是資源耗盡
工具:
- pstree
 - netstat
 
查看進(jìn)程創(chuàng)建的線程數(shù),以及網(wǎng)絡(luò)連接數(shù),如果資源耗盡,也可能出現(xiàn)OOM。 這里介紹另一種方法,通過(guò)
- /proc/${PID}/fd
 - /proc/${PID}/task
 
可以分別查看句柄詳情和線程數(shù)。 例如,某一臺(tái)線上服務(wù)器的sshd進(jìn)程PID是9339,查看
- ll /proc/9339/fd
 - ll /proc/9339/task
 
如上圖,sshd共占用了四個(gè)句柄:
- 0 -> 標(biāo)準(zhǔn)輸入
 - 1 -> 標(biāo)準(zhǔn)輸出
 - 2 -> 標(biāo)準(zhǔn)錯(cuò)誤輸出
 - 3 -> socket(容易想到是監(jiān)聽(tīng)端口)
 
sshd只有一個(gè)主線程PID為9339,并沒(méi)有多線程。
所以,只要
- ll /proc/${PID}/fd | wc -l
 - ll /proc/${PID}/task | wc -l (效果等同pstree -p | wc -l)
 
就能知道進(jìn)程打開(kāi)的句柄數(shù)和線程數(shù)。
希望這1分鐘能幫到這位星球水友。
【本文為51CTO專欄作者“58沈劍”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】



















 
 
 










 
 
 
 