Java内存泄漏需用可复现代码+可观测行为验证:静态集合累积、ThreadLocal未remove、监听器未注销、类加载器泄漏四类场景,分别通过jstat、jmap、MAT和jcmd精准定位。

Java 内存泄漏不能靠“猜”,得靠可复现的代码+可观测的行为。核心是让本该被回收的对象因强引用残留而长期驻留堆中,且这种增长在数分钟内能被工具明确捕获。
静态集合持续累积
这是最直观、最容易验证的泄漏模式。静态容器生命周期与类绑定,只要 JVM 不退出,它就一直存在。
- 用 static List
或 static Map 模拟缓存 - 每秒添加一个 1MB 的 byte[],不设上限也不清理
- 运行 3–5 分钟后,用 jstat -gc
观察老年代使用率是否阶梯上升;用 jmap -histo 查看 byte[] 实例数是否线性增长
ThreadLocal 未清理
线程池场景下最隐蔽也最典型的泄漏点:线程复用,但 ThreadLocal 中的数据没 remove,导致对象随线程长期存活。
- 创建固定大小线程池(如 5 个线程)
- 每个任务向 static ThreadLocal
放入 10MB 数据,不调用 remove() - 任务执行完后观察内存:用 Runtime.getRuntime().totalMemory() - freeMemory() 打印对比,会发现 GC 后内存不回落
- 补上 CACHE.remove() 再跑一次,内存占用明显下降,即可确认泄漏路径
监听器/回调未注销
GUI、事件总线或自定义发布订阅中常见。注册后忘记反注册,导致被监听对象无法释放。
- 定义一个静态事件总线(如 static List
listeners = new ArrayList() ) - 模拟“注册监听”:每轮循环 add 一个新 Runnable(内部持有一个大数组)
- 不提供 unregister 方法,也不清空 listeners
- 配合 jmap dump + MAT 的 Leak Suspects 报告,能直接看到 listeners 引用链拖住大量对象
类加载器泄漏(元空间级)
适用于 Spring Boot 热部署、OSGi 或字节码增强框架。泄漏的是 Class 和其 ClassLoader,最终撑爆 Metaspace。
- 用 ASM 或 Javassist 动态生成类,每次生成不同类名
- 为每个类创建独立的 URLClassLoader 加载(不能复用)
- 启动时加 -XX:MaxMetaspaceSize=32m,几轮生成后就会触发 java.lang.OutOfMemoryError: Metaspace
- jcmd
VM.native_memory summary 可看到 class 类内存持续上涨
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











