定位java内存泄漏需先用jstat观察老年代持续增长和fgc激增,再用jmap -histo:live定位高实例数/高字节数的可疑类,接着用mat分析堆转储,通过leak suspects report和dominator tree结合path to gc roots(排除弱软引用)找到强引用链,最后回归代码验证并修复静态集合、threadlocal、监听器或内部类等问题。

定位 Java 内存泄漏,核心是确认“本该被回收的对象为何还存活”,而堆分析工具正是用来揭示对象存活路径的关键手段。重点不在堆转储本身,而在如何读取它——关键看谁在强引用这些对象,以及这些引用是否合理。
先确认泄漏是否真实存在
别急着导堆转储。先用 jstat 看 GC 行为:
- 执行
jstat -gcutil <pid> 1000</pid>,持续观察 O(老年代使用率) 是否持续上升且不回落 - 同时关注 FGC(Full GC 次数) 是否短时间激增,但每次回收后 O 值几乎不变
- 若 O > 90% + FGC 频繁 + FGCT(Full GC 总耗时)不断变长,基本可判定存在内存泄漏
快速锁定可疑类
确认泄漏后,立即用 jmap -histo:live 查看当前存活对象分布:
- 命令:
jmap -histo:live <pid> | head -n 20</pid> - 重点关注 #instances 和 #bytes 都靠前的自定义类(如
com.example.OrderCache、Cms_Organization) - 留意数组类型:[C(字符串)、[[I(二维数组)、[Ljava.lang.Object;(集合底层数组),它们常是缓存或列表膨胀的信号
用 MAT 分析堆转储找根因
导出轻量级快照后,MAT 是最常用的离线分析工具:
- 导出命令:
jmap -dump:live,format=b,file=heap-$(date +%s).hprof <pid></pid>(加live只保留可达对象) - 打开文件后,优先运行 Leak Suspects Report,它会自动标出最可能的泄漏点
- 再查看 Dominator Tree,按 “Retained Heap” 排序,找到占用内存最大的对象链
- 对可疑对象右键 → Path to GC Roots → exclude weak/soft references,只看强引用路径
- 典型泄漏路径包括:static 字段持有集合、ThreadLocal 未清理、注册监听器未注销、非静态内部类意外延长外部类生命周期
结合代码验证与修复
分析结果必须回归源码验证:
- 若发现大量
RefreshCmsOrganizationStruts实例堆积,就去查对应 Runnable 是否被重复提交且未控制生命周期 - 若看到
ArrayList中User实例无限增长,检查是否有 while(true) 循环 add 且无清除逻辑 - 修复方向明确:清空静态集合、调用
threadLocal.remove()、显式注销监听器、改用静态内部类或弱引用等
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











