JVM内存泄漏分析的demo

本文参考:

JVM调优参数、方法、工具以及案例总结

JVM监控和调优常用命令工具总结 - Pickle - 博客园 (cnblogs.com)

面试官问我JVM调优,我忍不住了! - Java3y - 博客园 (cnblogs.com)

从实际案例聊聊Java应用的GC优化 (qq.com)

JVM调优的几种场景(建议收藏) (qq.com)

上面是在学习过程中参考到的各种文献,下面是动手去做一个内存泄漏分析的小demo

场景模拟

  1. 编写一个会有内存泄漏的场景,在下面我是模拟了一个线程池不销毁并且不停创建非核心线程(非核心线程在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);
                }
            });
        }
    }
}

  1. 命令行操作,编译文件

    javac MemoryLeak.java   
    
  2. 命令行操作,执行文件,并且-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文件,需要重试或者耐心等待

  3. 观察输出

    ...
    [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.(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

  1. File->Open Heap Dump… 查看刚刚的heap.bin文件

  1. 选择其中的泄漏报告 Leak Suspects

可以看到Problem Suspect 1 2 3列出了几个可能发生内存泄漏的对象,从1和3可以看出,竟然有1435个Thread对象以及144个ThreadPoolExecutor对象,确实占有了大量内存。

结论也可以得出来:

每个ThreadPoolExecutor创建了10个线程,每个线程的在不处理任务后的60s会被回收,线程池此时也会一直存在等待接受新的任务。又由于外部一直在while(true)创建新的线程池,导致这一分钟以内,堆积了大量被创建的线程池以及其创建的线程。

你可能感兴趣的:(Java学习之路,jvm,java)