1、我們手寫一個內(nèi)存泄露的案例,來體驗一下
用我們比較熟悉的Spring來實現(xiàn),定義一個Bean,實現(xiàn)InitializingBean接口,在afterPropertiesSet方法中定時每隔1秒鐘生成一批對象,加入到list中
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Created by martin.xie on 2020/4/28.
*/
@Component
public class MemLeaker implements InitializingBean {
private List<Object> objs = new LinkedList<>();
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
@Override
public void afterPropertiesSet() throws Exception {
service.scheduleAtFixedRate(() -> {
System.out.println("start add obj...");
for (int i = 0; i < 50000; i++) {
objs.add(new Object());
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
}
2、啟動容器,使用如下啟動參數(shù)
-XX:+PrintGCDetails
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=128m
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationStoppedTime
-Xloggc:D:/loggc/gc.log
-XX:-OmitStackTraceInFastThrow
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:/loggc
-XX:+PrintCommandLineFlags
-XX:+PrintFlagsFinal
-Xms256m -Xmx256m -Xmn64m
3、過2分鐘后查看控制臺,出現(xiàn)GC overhead limit exceeded,這個提示大多數(shù)是意味著系統(tǒng)內(nèi)存泄露導(dǎo)致最后溢出了。
因為spring容器中的bean MemLeaker 只要不銷毀就會一直存在,bean的objs屬性中的對象也沒辦法被回收。不斷地增加對象到list中就會導(dǎo)致內(nèi)存占用持續(xù)增加,GC不停地進行回收卻沒有效果
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to D:/loggc\java_pid9380.hprof ...
Heap dump file created [433345651 bytes in 2.296 secs]
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "Timer-0" java.lang.OutOfMemoryError: GC overhead limit exceeded
4、從gc log中也可以發(fā)現(xiàn)GC的結(jié)果,[ParOldGen: 196139K->196138K(196608K)] 老年代回收前后所占用的內(nèi)存非常接近,而且占用量接近196608總的內(nèi)存數(shù)
2020-04-29T18:05:17.436+0800: 301.896: Total time for which application threads were stopped: 0.4551662 seconds, Stopping threads took: 0.0000148 seconds
2020-04-29T18:05:17.436+0800: 301.896: [Full GC (Ergonomics) [PSYoungGen: 48639K->48639K(56832K)] [ParOldGen: 196140K->196137K(196608K)] 244780K->244777K(253440K), [Metaspace: 60852K->60852K(1103872K)], 0.4698420 secs] [Times: user=1.12 sys=0.00, real=0.47 secs]
2020-04-29T18:05:17.906+0800: 302.366: Total time for which application threads were stopped: 0.4701188 seconds, Stopping threads took: 0.0000298 seconds
2020-04-29T18:05:17.907+0800: 302.367: [Full GC (Ergonomics) [PSYoungGen: 48639K->48639K(56832K)] [ParOldGen: 196137K->196136K(196608K)] 244777K->244776K(253440K), [Metaspace: 60852K->60852K(1103872K)], 0.7593071 secs] [Times: user=1.58 sys=0.00, real=0.76 secs]
2020-04-29T18:05:18.666+0800: 303.126: Total time for which application threads were stopped: 0.7595541 seconds, Stopping threads took: 0.0000183 seconds
2020-04-29T18:05:18.667+0800: 303.127: [Full GC (Ergonomics) [PSYoungGen: 48640K->48639K(56832K)] [ParOldGen: 196139K->196138K(196608K)] 244779K->244778K(253440K), [Metaspace: 60852K->60852K(1103872K)], 0.7987929 secs] [Times: user=1.50 sys=0.00, real=0.80 secs]
2020-04-29T18:05:19.466+0800: 303.926: Total time for which application threads were stopped: 0.7990989 seconds, Stopping threads took: 0.0000302 seconds
5、分析heapdump文件, 從D:\loggc\java_pid9380.hprof找到剛生成的dump文件,用MAT工具打開
Overview里面看到內(nèi)存占用情況的餅圖,以及Histogram、 Dominator Tree、 Leak Suspects 各種報告的入口。

其中Histogram就是各種類實例對象的個數(shù)以及占用的內(nèi)存大小,打開可以發(fā)現(xiàn)排在第一的就是java.util.LinkedList$Node。

然后MAT工具自帶內(nèi)存泄露的分析,打開Leak Suspects就可以看到problem,里面提到MemLeaker這個實例中的LinkedList這個實例累計占用了72.49%的內(nèi)存。點擊Details可以查看詳情

