本文参考:
JVM调优参数、方法、工具以及案例总结
JVM监控和调优常用命令工具总结 - Pickle - 博客园 (cnblogs.com)
面试官问我JVM调优,我忍不住了! - Java3y - 博客园 (cnblogs.com)
从实际案例聊聊Java应用的GC优化 (qq.com)
JVM调优的几种场景(建议收藏) (qq.com)
上面是在学习过程中参考到的各种文献,下面是动手去做一个内存泄漏分析的小demo
场景模拟
- 编写一个会有内存泄漏的场景,在下面我是模拟了一个线程池不销毁并且不停创建非核心线程(非核心线程在60s不用就会自动destory)的例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/9/17
*/
public class MemoryLeak {
public static void main(String[] args) {
MemoryLeak memoryLeak = new MemoryLeak();
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
memoryLeak.run();
}
}
private void run() {
// 不断创造非核心线程
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.submit(()->{
// System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
-
命令行操作,编译文件
javac MemoryLeak.java
-
命令行操作,执行文件,并且
-Xms1m -Xmx1m
表示运行的初始堆大小1m,最大堆大小也是1m,这些参数较小是为了有意构造OOM场景,-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.bin
是为了能够dump出OOM日志java -Xms1m -Xmx1m -XX:+PrintGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.bin MemoryLeak
注意⚠️:dump文件太大,一下子不能够产生heap.bin文件,需要重试或者耐心等待
-
观察输出
... [Full GC (Ergonomics) 997K->997K(1536K), 0.0150748 secs] at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674) [Full GC (Ergonomics) 997K->992K(1536K), 0.0153944 secs] at java.lang.StringBuilder.append(StringBuilder.java:214) at java.util.concurrent.Executors$DefaultThreadFactory.newThread(Executors.java:613) at java.util.concurrent.ThreadPoolExecutor$Worker.<init>(ThreadPoolExecutor.java:619) [Full GC (Ergonomics) 997K->992K(1536K), 0.0156156 secs] at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:932) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1378) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112) at MemoryLeak.run(MemoryLeak.java:26) at MemoryLeak.main(MemoryLeak.java:18) [Full GC (Ergonomics) 997K->989K(1536K), 0.0149037 secs] [Full GC (Ergonomics) 997K->989K(1536K), 0.0146840 secs] [Full GC (Ergonomics) 997K->989K(1536K), 0.0138023 secs] [Full GC (Ergonomics) 997K->989K(1536K), 0.0146598 secs] [Full GC (Ergonomics) 997K->989K(1536K), 0.0140917 secs] [Full GC (Ergonomics) 997K->989K(1536K), 0.0141075 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0138773 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0137902 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0134941 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0139297 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0138875 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0136721 secs] [Full GC (Ergonomics) 997K->990K(1536K), 0.0135201 secs] [Full GC (Ergonomics) 997K->991K(1536K), 0.0138388 secs] ... [Full GC (Ergonomics) 997K->995K(1536K), 0.0135345 secs] [Full GC (Ergonomics) 997K->995K(1536K), 0.0135026 secs] [Full GC (Ergonomics) 997K->995K(1536K), 0.0130507 secs] [Full GC (Ergonomics) 997K->995K(1536K), 0.0128278 secs] [Full GC (Ergonomics) 997K->996K(1536K), 0.0138620 secs] [Full GC (Ergonomics) 997K->996K(1536K), 0.0129367 secs]
打印出了Full GC的过程,但是占用的内存还是越来越多,确实发生了内存泄漏,初步猜测就是线程创建后一直没有销毁,线程池也没有shutdown
问题分析
借助MAT分析上面dump下来的heap.bin文件
去官网下载就好 -> https://eclipse.dev/mat/downloads.php
- File->Open Heap Dump… 查看刚刚的heap.bin文件
- 选择其中的泄漏报告 Leak Suspects
可以看到Problem Suspect 1 2 3列出了几个可能发生内存泄漏的对象,从1和3可以看出,竟然有1435个Thread对象以及144个ThreadPoolExecutor对象,确实占有了大量内存。
结论也可以得出来:
每个ThreadPoolExecutor创建了10个线程,每个线程的在不处理任务后的60s会被回收,线程池此时也会一直存在等待接受新的任务。又由于外部一直在while(true)创建新的线程池,导致这一分钟以内,堆积了大量被创建的线程池以及其创建的线程。