堆转储如何分析?

访客 性能优化 1

本文目录导读:

  1. 第一步:获取堆转储文件
  2. 第二步:选择合适的分析工具
  3. 第三步:核心分析流程(以MAT为例)
  4. 常见问题快速定位表
  5. 分析七步曲
  6. ⚠️ 注意事项

分析堆转储(Heap Dump)是定位Java内存泄漏、内存溢出(OOM)以及对象过多等问题的核心手段,主要流程是获取快照 -> 加载分析 -> 寻找异常

以下是标准分析步骤和常用工具:

第一步:获取堆转储文件

在分析之前,你需要先拿到 .hprof 文件。

  1. 自动生成(推荐):在 -XX:+HeapDumpOnOutOfMemoryError 参数下运行应用,发生OOM时会自动生成。
  2. 手动触发(jmap)
    # 生成堆转储
    jmap -dump:live,format=b,file=heap.hprof <pid>
  3. jcmd
    jcmd <pid> GC.heap_dump heap.hprof
  4. 故障排查工具: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) 进一步分析:
    • 直方图:按类名排序,看哪些类的实例数量最多、占用总内存最大,关注自定义的业务类(如 UserOrder)的数量是否异常。
    • 支配树(更关键):分析占用内存最多的对象是谁,以及它们保留(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块

分析七步曲

  1. 获取:有条件用 -XX:+HeapDumpOnOutOfMemoryError
  2. 加载:用MAT/Eclipse(或 jhat,不推荐)打开。
  3. 看报告:检查 Leak Suspects。
  4. 找大对象:在支配树/Dominator Tree找内存消费大户。
  5. 追根源:对大对象做 Path to GC Roots(排除虚/弱引用)。
  6. 看引用链:定位是哪个全局变量(通常是静态字段或ThreadLocal)引用了这个巨量对象。
  7. 验证:OQL查询集合并分析内容是否合理,然后看线程栈确认代码位置。

⚠️ 注意事项

  • 安全:堆转储里可能有明文密码、用户隐私数据,分析后务必立刻删除
  • 文件大小:如果堆有4G,转储文件通常也有4G+,加载时 MAT-Xmx 需要设为6G-8G(因为需要构建对象图)。
  • 离线分析:生产环境尽量把转储文件拉到开发环境分析,不要在线上机器直接打开大堆转储(会引发更严重卡顿)。

标签: 内存泄漏

抱歉,评论功能暂时关闭!