分享一次解決線上java應用導致JVM記憶體溢位(OOM)的問題
某個線上的應用執行幾天後,總是出現卡死甚至出現OOM的情況。
注:文中圖片可能與描述不符,僅作為演示!
通過Linux的top命令檢視cpu佔比
首先通過top
命令檢視,發現某個java程式佔用了較高記憶體:
JDK的jps命令確定是哪個java程式
然後通過jps -l
與上面的PID列(2848)比較,確定是 picasso-java-v1.jar 這個java程式佔用cpu過高:
通過ps 檢視具體哪個JVM執行緒
當時想的是可能應用內某個執行緒導致死迴圈,使用如下命令檢視2848程序的各個執行緒小號cpu時間
//ps -mp [執行緒號] -o THREAD,tid,time
ps -mp 2848 -o THREAD,tid,time
下圖 %CPU列 為 cpu的百分比,TID列 為執行緒id
找到消耗cpu最大的執行緒(當時線上出現時某個執行緒消耗cpu90%多),這裡為了演示,所以取2858這個執行緒。
通過jstack檢視java中的具體執行緒棧資訊
然後把上面執行緒id轉化為16進位制,在shell中使用printf "%x\n" tid
即可,結果為b2a
:
然後使用jstack輸出這個執行緒的呼叫棧:
//jstack [程序id] | grep [執行緒的16進位制id] -A行數
jstack 2848 | grep b2a -A30
發現為GC執行緒,原來是jvm記憶體回收導致的cpu過高!
通過jstat檢視記憶體回收情況
使用jstat -gcutil 執行緒數 間隔秒數 次數
命令檢視:
如圖上面的FGC列Full GC次數為幾百,而FGCT的Full GC秒數達到了幾千,通過設定更多的監控次數觀察,每次Full GC過後,O列的老年代還是99%,可見是記憶體不足導致的一直不停Full GC !
重啟程式,使用-Xmx -Xms設定更大堆記憶體
通過重啟程式,-Xmx2048m -Xms2048m
設定了更大的記憶體引數,緩解了問題!
問題重現,尋找其他原因,使用jmap生成堆轉儲檔案
隔了幾天後,問題重現,此時通過jmap 生成了映象
jmap -dump:format=b,file=dumpfile.dat [pid]
生成的檔案也是非常之大,達到2.1Gb!
柳暗花明,使用Eclipse Memory Analyzer分析出原因
把dump檔案下載到本地,同時下載了Eclipse Memory Analyzer對dump檔案進行分析。
在Eclipse Memory Analyzer中生成Leak Suspects報告:
發現是 PoolingHttpClientConnectionManager 這個類導致的。再點選上圖中的Details,檢視詳細資訊:
這下清晰了,是阿里的oss類庫導致的,結合程式中的如下程式碼:
OSSClient ossClient = new OSSClient("","");
PutObjectResult putObjectResult = ossClient.putObject("", "", "");
這個方法在程式中沒有使用單例模式而且沒有關閉,每呼叫一次就生成了一個PoolingHttpClientConnectionManager,而且是不可回收的。通過原始碼檢視到IdleConnectionReaper.size()這個類會生成PoolingHttpClientConnectionManager的總數量。
驗證猜測
使用 -Xms20m -Xmx20m 執行以下程式,發現size一直變大,最後導致OOM (java.lang.OutOfMemoryError)
for (int i = 0; i < 60000; i++) {
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject("<test-bucket>", "test1234" + UUID.randomUUID(), new File("d:/file.txt"));
System.out.println("size="+IdleConnectionReaper.size());
Thread.sleep(2);
}
檢視api,得知使用shutdown方法即可關閉OSSClient:
ossClient.shutdown();
再執行以下程式,size一直為0,一切正常:
for (int i = 0; i < 60000; i++) {
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject("<test-bucket>", "test1234" + UUID.randomUUID(), new File("d:/file.txt"));
ossClient.shutdown();
System.out.println("size="+IdleConnectionReaper.size());
Thread.sleep(2);
}
至此,終於找到了導致cpu過高和OutOfMemoryError的真凶!