編寫一個簡單的內(nèi)存泄露的栗子
public class BufferCache {
private static Queue<byte[]> noUseData = new LinkedBlockingQueue<>();
private static Queue<byte[]> usedData = new LinkedBlockingQueue<>();
// 申請內(nèi)存
public static byte[] allot() {
byte[] buffer = noUseData.poll();
if(buffer == null) {
buffer = new byte[1024 * 128]; // 1/8M
usedData.add(buffer);
System.out.println(usedData.size() + " - " + buffer.hashCode());
}
return buffer;
}
// 釋放內(nèi)存
public static void release(byte[] buffer) {
usedData.remove(buffer);
noUseData.add(buffer);
}
}
public class MemoryOutTest {
public static void main(String[] args) {
while (true) {
byte[] buffer = BufferCache.allot();
// 休眠線程杯拐,忽略sleepIgnoreException異常
ThreadUtil.sleepIgnoreException(500);
// 不釋放內(nèi)存
// BufferCache.release(buffer);
}
}
}
啟動程序時霞篡,添加vm參數(shù)-Xms30M -Xmx30M -XX:+HeapDumpOnOutOfMemoryError
世蔗。
-XX:+HeapDumpOnOutOfMemoryErro
配置了發(fā)生OOM時自動生成dump文件。
jstat
$ jstat -gc 3145 1000 50
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7239.5 20480.0 0.0 4480.0 779.2 384.0 76.4 0 0.000 0 0.000 - - 0.000
1024.0 1024.0 0.0 0.0 8192.0 8007.6 20480.0 0.0 4480.0 779.2 384.0 76.4 0 0.000 0 0.000 - - 0.000
// Young GC
1024.0 1024.0 0.0 992.1 8192.0 669.3 20480.0 5776.7 4864.0 3340.3 512.0 367.6 1 0.003 0 0.000 - - 0.003
...
1024.0 1024.0 0.0 992.1 8192.0 6896.0 20480.0 5776.7 4864.0 3340.3 512.0 367.6 1 0.003 0 0.000 - - 0.003
1024.0 1024.0 0.0 992.1 8192.0 7664.1 20480.0 5776.7 4864.0 3340.3 512.0 367.6 1 0.003 0 0.000 - - 0.003
// Young GC&Full GC
1024.0 1024.0 0.0 0.0 8192.0 450.5 20480.0 14708.8 4864.0 3343.5 512.0 367.6 2 0.007 1 0.009 - - 0.016
1024.0 1024.0 0.0 0.0 8192.0 1271.9 20480.0 14708.8 4864.0 3343.5 512.0 367.6 2 0.007 1 0.009 - - 0.016
...
1024.0 1024.0 0.0 0.0 8192.0 7416.6 20480.0 14708.8 4864.0 3343.5 512.0 367.6 2 0.007 1 0.009 - - 0.016
1024.0 1024.0 0.0 0.0 8192.0 8184.7 20480.0 14708.8 4864.0 3343.5 512.0 367.6 2 0.007 1 0.009 - - 0.016
1024.0 1024.0 0.0 0.0 8192.0 3230.2 20480.0 20214.9 4864.0 3353.0 512.0 367.6 2 0.007 2 0.016 - - 0.023
...
1024.0 1024.0 0.0 0.0 8192.0 7710.7 20480.0 20214.9 4864.0 3353.0 512.0 367.6 2 0.007 2 0.016 - - 0.023
// Full GC
1024.0 1024.0 0.0 0.0 8192.0 8192.0 20480.0 20470.9 4864.0 3362.5 512.0 367.6 2 0.007 5 0.049 - - 0.056
-gc
表示輸出gc信息朗兵,后面兩個參數(shù)1000 50
表示每個1000毫秒輸出一個結(jié)果污淋,直到輸出50次結(jié)果。
YGC表示Young GC次數(shù)余掖,F(xiàn)GC表示Full GC次數(shù)寸爆,關(guān)注他們數(shù)量變化。
可以看到盐欺,前兩次Young GC赁豆,EU(Eden區(qū)已使用內(nèi)存)減少,OU(Old區(qū)已使用內(nèi)存)增加冗美。
但Full GC沒有導(dǎo)致OU減少魔种,最后OU不足,進行了多次Full GC粉洼,OU依然不足节预,發(fā)生OOM了。
jstat使用可參考:
Oracle JDK 9 Documentation - jstat
jmap
jmap -histo[:live] <pid>
打印指定進程的java對象堆的直方圖
如果指定了“l(fā)ive”參數(shù)漆改,則僅展示存活對象
jmap -dump:[live,]format=b,file=heap.bin <pid>
生成dump文件
jmap使用可參考:
Oracle JDK 9 Documentation - jmap
mat
MAT(EclipseMemoryAnalyzer)是eclipse中的一個插件心铃,是分析dump文件的利器准谚。
下面的分析主要是參考這篇文章如何使用MAT進行內(nèi)存泄露分析
使用MAT打開dump文件(可以使用jmap生成或者OOM時自動生成)
LeakSuspects界面列舉內(nèi)存泄漏的可疑點挫剑,
點擊Problem Suspect 1的Details
這里給出了內(nèi)存泄露的類。
打開Histogram界面柱衔,按RetainedHeap排序樊破,很快就可以看到內(nèi)存泄露的類。
為什么按RetainedHeap排序唆铐,因為ShallowHeap是對象本身占用內(nèi)存哲戚,而RetainedHeap是對象本身加所有引用對象占用內(nèi)存之和。
再按ShallowHeap排序艾岂,可以看到byte[]本身占用的內(nèi)存最大顺少,
點擊byte[]對象,在左邊Inspector界面的byte[]項右擊王浴,選擇Path to GC Roots ---> exclude all phantom/weak/soft etc.references
可以查看從GC Roots對象到選中對象的所有路徑
這里也可以找到內(nèi)存泄露的類脆炎。
最后看看內(nèi)存快照對比,我在程序OOM前使用jmap生成了一份dump文件氓辣,與OOM時生成的dump文件對比
這里可以看到byte[]沒有內(nèi)存回收秒裕,泄露了。