分析 .hprof 文件需三步:先验证快照质量(看 Total Heap、Objects 数、GC Roots 是否异常),再用 Histogram 筛选高 Retained Heap 与高 Objects 类,最后通过 Dominator Tree 追 Path to GC Roots 排查不可回收路径,并建议多快照对比提升准确性。

直接打开 .hprof 文件后,别急着看泄漏报告。重点是三步:先确认快照本身是否可信,再筛出异常类,最后顺着引用链追到 GC Root。
第一步:验证快照质量,避开假信号
打开文件后先看 Overview 页顶部三项:
- Total Heap 接近你设的 -Xmx 值(比如 -Xmx4g 却显示 3.92g),说明堆长期高位运行,回收不力
- Number of Objects 明显高于业务常态(如常规 200 万对象,快照里有 800 万),大概率存在堆积
- GC Roots 数量超过 3000 就要警惕——ThreadLocal、静态集合未清、JNI 引用残留都可能推高这个值
第二步:用 Histogram 锁定“高 Retained Heap + 高 Objects”类
这是按类统计内存占用的核心视图,盯紧两列:
- Retained Heap:该类所有实例支配的总内存(含它引用的所有下游对象)
- Objects:该类当前存活实例数量
- 典型线索:java.util.HashMap Retained Heap 占比高但实例少 → 很可能被 static 持有,内部塞了大量数据;byte[] 或 char[] 实例数上万且占比突增 → 可能是日志缓存、未关闭流或 Base64 解码后没释放;自定义类如 OrderDetail 实例超 10 万+ 且 Retained Heap 排前三 → 查是否误存进全局 Map 或 Session
第三步:从 Dominator Tree 追 Path to GC Roots,确认不可回收路径
选中 Histogram 中可疑类的一行,右键 → List objects → with outgoing references,挑一个大对象,再右键 → Path to GC Roots → exclude all phantom/weak/soft references。
- 路径终点停在 java.lang.Thread(尤其是 http-nio-xxx-exec-xx)→ 检查线程局部变量或异步任务未完成
- 停在 ThreadPoolExecutor → 查任务队列是否堆积、Runnable 是否持有外部大对象
- 停在 RequestContextHolder 或自定义 ClassLoader → 很可能是 Web 上下文未清理或热部署残留
补充建议:多快照对比更可靠
单张快照容易受瞬时波动干扰。如果条件允许,间隔几分钟生成两份 live 快照(比如用 arthas 的 heapdump 命令),对比增长最快的类和 Retained Heap 变化,能更精准锁定泄漏源头。


















