本文目录导读:
分析堆转储(Heap Dump)是定位Java内存泄漏、内存溢出(OOM)以及对象过多等问题的核心手段,主要流程是获取快照 -> 加载分析 -> 寻找异常。
以下是标准分析步骤和常用工具:
第一步:获取堆转储文件
在分析之前,你需要先拿到 .hprof 文件。
- 自动生成(推荐):在
-XX:+HeapDumpOnOutOfMemoryError参数下运行应用,发生OOM时会自动生成。 - 手动触发(jmap):
# 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid>
- jcmd:
jcmd <pid> GC.heap_dump heap.hprof
- 故障排查工具:Arthas(
heapdump命令)、VisualVM。
第二步:选择合适的分析工具
- Eclipse MAT(Memory Analyzer Tool,最专业):最强、最深入,能自动计算可疑泄漏点。
- VisualVM(免费可视化,适合快速浏览):适合快速查看大对象、线程栈和GC Roots。
- JProfiler / YourKit(商业软件):功能全面,代价昂贵。
- 在线分析(谨慎使用):HeapHero(在线)、GCeasy(在线)。注意:严禁将涉及用户信息的堆转储上传到第三方公网服务。
第三步:核心分析流程(以MAT为例)
加载文件
- 直接打开
.hprof文件,如果文件非常大(几个GB),需要在MemoryAnalyzer.ini中调大-Xmx。
查看“嫌疑犯”报告
- 打开后,MAT会自动弹出 Leak Suspects Report(泄漏嫌疑报告),这是最快的一步。
- 看什么:报告会明确告诉你:“系统保留的最大对象是一个java.util.HashMap,它占用了整个堆的80%”,点击查看“Shortest Paths To the Accumulation Point”(到聚合点的最短路径),可以看见谁持有引用导致它无法被GC回收。
大对象与主导树
- 如果嫌疑报告不明确,需要通过直方图(Histogram) 和支配树(Dominator Tree) 进一步分析:
- 直方图:按类名排序,看哪些类的实例数量最多、占用总内存最大,关注自定义的业务类(如
User、Order)的数量是否异常。 - 支配树(更关键):分析占用内存最多的对象是谁,以及它们保留(Retained Set)了哪些子对象,这能准确找到一个巨大的对象根(例如一个全局Map、一个Cache、一个Session集合)。
- 直方图:按类名排序,看哪些类的实例数量最多、占用总内存最大,关注自定义的业务类(如
分析GC Roots
- 核心问题:一个对象如果不可达于GC Roots,它会被回收,如果该对象内存很大,通常是因为它被某个全局的GC Roots引用。
- 操作:在直方图或支配树中,右键点击怀疑对象 ->
Merge Shortest Paths to GC Roots-> 选择exclude all phantom/weak/soft etc. references(排除软引用、弱引用)。 - 结果:你会看到一串引用链,
Thread (主线程) -> HashMap (静态变量 cache) -> Entry -> YourBusinessObject (持续增加)这通常就是根因——静态持有、ThreadLocal、Spring/Guava Cache未回收等。
查看集合详情
- 很多问题出在Map或List内部,MAT可以直接查看:
- 选中
java.util.HashMap-> 右键 ->List objects->with incoming references。 - 然后点开
Entries选项卡,直接看到HashMap里所有的Key和Value,判断是否有大量无效或重复数据。
- 选中
配合其他信息
- 线程栈(Thread OverView):看哪个线程在创建这些对象,通常OOM发生在某个请求进入大量数据或无限循环创建时。
- OQL(对象查询语言):相当于SQL查堆,比如查所有字符串长度超过1000的字符串:
SELECT * FROM java.lang.String s WHERE s.count > 1000
常见问题快速定位表
| 现象 | 堆转储特征 | 根因可能性 | 处理方向 |
|---|---|---|---|
| Full GC频繁 | 存活对象一直在增长,GC后无法显著下降 | 内存泄漏:全局缓存、未关闭资源、ThreadLocal | 查看动态增长的集合和静态变量 |
| OOM: Java heap space | 堆转储文件巨大,基本无法打开(需要远大于堆内存的内存来加载) | JVM堆配置不足或瞬间海量请求 | 调整 -Xmx,或优化业务逻辑/限流 |
| OOM: GC overhead limit exceeded | MAT中能看到大量的 GC Roots 和不可回收的对象 | 存活对象过多,GC一直在试图回收但效率极低 | 排查不可回收对象为何大量存在 |
| 大量重复对象 | 直方图中某个类实例数量远高于业务猜想 | 线程安全问题(如多线程put导致HashMap丢数据/环)或编码bug(new了太多对象) | 检查集合使用和对象创建逻辑 |
| 堆内存持续缓慢增长 | MAT中 java.lang.ref.Finalizer 对象很多 |
资源未正确关闭(流、连接、文件)导致Finalizer堆积 | 检查try-with-resources或finally块 |
分析七步曲
- 获取:有条件用
-XX:+HeapDumpOnOutOfMemoryError。 - 加载:用MAT/Eclipse(或
jhat,不推荐)打开。 - 看报告:检查 Leak Suspects。
- 找大对象:在支配树/Dominator Tree找内存消费大户。
- 追根源:对大对象做
Path to GC Roots(排除虚/弱引用)。 - 看引用链:定位是哪个全局变量(通常是静态字段或ThreadLocal)引用了这个巨量对象。
- 验证:OQL查询集合并分析内容是否合理,然后看线程栈确认代码位置。
⚠️ 注意事项
- 安全:堆转储里可能有明文密码、用户隐私数据,分析后务必立刻删除。
- 文件大小:如果堆有4G,转储文件通常也有4G+,加载时
MAT的-Xmx需要设为6G-8G(因为需要构建对象图)。 - 离线分析:生产环境尽量把转储文件拉到开发环境分析,不要在线上机器直接打开大堆转储(会引发更严重卡顿)。
标签: 内存泄漏