1、我们手写一个内存泄露的案例,来体验一下
用我们比较熟悉的Spring来实现,定义一个Bean,实现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、启动容器,使用如下启动参数
-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分钟后查看控制台,出现GC overhead limit exceeded,这个提示大多数是意味着系统内存泄露导致最后溢出了。
因为spring容器中的bean MemLeaker 只要不销毁就会一直存在,bean的objs属性中的对象也没办法被回收。不断地增加对象到list中就会导致内存占用持续增加,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中也可以发现GC的结果,[ParOldGen: 196139K->196138K(196608K)] 老年代回收前后所占用的内存非常接近,而且占用量接近196608总的内存数
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里面看到内存占用情况的饼图,以及Histogram、 Dominator Tree、 Leak Suspects 各种报告的入口。
其中Histogram就是各种类实例对象的个数以及占用的内存大小,打开可以发现排在第一的就是java.util.LinkedList$Node。
然后MAT工具自带内存泄露的分析,打开Leak Suspects就可以看到problem,里面提到MemLeaker这个实例中的LinkedList这个实例累计占用了72.49%的内存。点击Details可以查看详情