JVM内存泄漏排查速查手册核心是“快准稳”:第一步看OOM类型锁定泄漏区域;第二步用jstat/jmap/jstack命令5秒内获取关键证据;第三步用MAT分析Dominator Tree和GC Roots定位根因;第四步通过jstat监控、类加载数对比、jmap -histo及GC日志验证修复效果。

JVM内存泄漏排查速查手册的核心是“快准稳”——不讲原理铺垫,不堆工具列表,只保留线上出问题时真正能立刻执行的判断路径和命令动作。
第一步:看现象,快速锁定泄漏区域
别一上来就 dump 堆。先用三秒确认问题在哪一层:
- 报 java.lang.OutOfMemoryError: Java heap space → 堆泄漏(占80%以上)
- 报 java.lang.OutOfMemoryError: Metaspace → 元空间泄漏(类加载器或动态字节码问题)
- 报 java.lang.OutOfMemoryError: Direct buffer memory → 直接内存泄漏(NIO ByteBuffer未释放)
- 报 java.lang.OutOfMemoryError: unable to create new native thread → 线程栈泄漏(-Xss 过大 or 线程数失控)
- 系统级告警:内存持续上涨但 JVM 堆监控平稳 → 可能是内核层或本地内存泄漏(如 JNI、驱动、glibc malloc)
第二步:用命令,5秒内拿到关键证据
生产环境禁用图形化工具,所有操作必须命令行、低开销、可脚本化:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
-
查堆使用趋势:
jstat -gcutil <pid> 2s 5</pid>→ 看老年代(OU)是否单向上涨、FGC 是否频繁且无效 -
查类加载增长:
jstat -class <pid></pid>→ 看 loaded class 数是否持续上升(元空间泄漏强信号) -
查直接内存:
jstat -gccapacity <pid></pid>中的 MCM/MCU 字段(Metaspace Capacity/Used),配合jcmd <pid> VM.native_memory summary</pid> -
紧急 dump 堆:
jmap -dump:live,format=b,file=heap_$(date +%s).hprof <pid></pid>(加 live 避免冗余对象) -
顺手抓线程快照:
jstack <pid> > thread_$(date +%s).txt</pid>(防止后续线程状态丢失)
第三步:定位根因,直奔 GC Root
拿到 heap.hprof 后,跳过所有中间分析,打开 Eclipse MAT,只做三件事:
- 点开 Dominator Tree → 找到占用 Top 3 的对象类型(如 HashMap$Node、byte[]、ConcurrentHashMap$Node)
- 对最大对象右键 → Path to GC Roots → with all references → 看谁在强引用它
- 重点检查这几类引用链:
- static 字段(尤其 Map/List/Cache 类型)
- ThreadLocalMap 中的 value(线程池场景下极易残留)
- ClassLoader 实例(若发现大量不同 ClassLoader,基本确定元空间泄漏)
- 监听器、回调注册表(Spring Context、EventBus、Netty ChannelHandler)
第四步:验证修复,避免“以为修好了”
改完代码后,不能只看不 OOM 就算成功。必须验证内存是否真正回落:
- 重启后开启监控:
jstat -gcutil <pid> 5s</pid>,观察老年代(OU)是否在业务稳定后趋于平稳(非缓慢爬升) - 对比两次 dump 的 class 加载数:
jstat -class <pid></pid>,确认无新增类持续加载 - 压力复现相同路径,用
jmap -histo <pid></pid>对比对象实例数变化,确认疑似泄漏类数量不再累积 - 加 JVM 参数
-XX:+PrintGCDetails -Xloggc:gc.log,确认 Full GC 后老年代回收量明显(不是“GC了但没释放”)

















