MAT 的 Dominator Tree / Leak Suspect 视图
一句话速记
MAT 分析 heap dump 的核心三板斧:① Overview(总览 → Leak Suspects 自动报告 → 看 pie chart 哪个对象占内存最多);② Dominator Tree(谁支配了最多的 retained heap → 按 retained 降序排列 → 找 GC Root 到该对象的引用链);③ Histogram(按类聚合 → 找实例数异常多的类 → List objects → with outgoing references 看谁持有它们)。
通俗解释(5 分钟版)
MAT 分析 heap dump 就像查账本:
Overview / Leak Suspects = 财务总监说:「这个部门开销最大,重点查它」
Dominator Tree = 查账本明细:「这笔 3M 的开销是谁批的?」
→ 沿着支配链往上找批准人(GC Root)
Histogram = 按科目分类统计:「手机费这个月为什么是平时的 100 倍?」
→ 某个类的实例数异常飙升
三种视图的关系:Overview 告诉你查谁,Dominator Tree 告诉你谁持有它,Histogram 帮你发现不该这么多实例的异常类。
关键细节
1)Leak Suspects(自动分析报告)
MAT 打开 heap dump 后的第一步。自动分析大对象持有链。
操作:File → Open Heap Dump → 选择 .hprof 文件
→ 自动生成 Leak Suspects Report
报告结构:
- Problem Suspect 1: 一个嫌疑对象占用了 XXX MB (XX%)
- Shortest Paths To the Accumulation Point: 谁引用了这个大对象
- Accumulated Objects in Dominator Tree: 该嫌疑对象支配了哪些子对象
解读要点:
- 如果 Suspect 是「业务对象」(如 Order、User) → 业务泄漏
- 如果 Suspect 是「框架对象」(如 Session、Connection) → 框架资源没释放
- 如果 Suspect 是「HashMap$Node / LinkedHashMap$Entry」→ 集合类无限增长
Leak Suspects 的局限性:只能找到「一个大对象持有所有小对象」的模式。对于 ThreadLocal 泄漏(每个线程独立泄漏),Leak Suspects 可能漏报——此时用 Histogram。
2)Dominator Tree(支配树)
核心概念:
支配关系:对象 A 支配对象 B = 所有到 B 的引用路径都经过 A
→ A 回收了,B 一定回收
→ A 的 retained heap = A 自己 + 所有被 A 支配的对象
GC Root(不被任何对象引用的起点):
- System Class(被 Bootstrap ClassLoader 加载的类)
- Thread(活跃线程 → ThreadLocal 值的持有者)
- JNI Global Reference
- Monitor Used(synchronized 锁持有的对象)
操作流程:
1. 打开 Dominator Tree 视图(工具栏第二个图标)
2. 按 Retained Heap 降序排列
→ 展开 retained 最大的节点
→ 一层层往下找"你预期不应该这么大"的对象
3. 对大对象右键 → Path To GC Roots → exclude weak/soft/phantom references
→ 看到完整的引用链:GC Root → ... → 大对象
→ 引用链上的「第一个业务代码」就是泄漏点
4. 排除弱引用(exclude weak references):弱引用不影响 GC,排除后引用链更干净
典型泄漏的 Dominator Tree 特征:
观察 结论
────────────────────────────────────────────────────
HashMap$Node 的 retained 占 > 50% → 某个 Map 无限增长
某个 Thread → ThreadLocalMap → Entry → ThreadLocal 泄漏
ArrayList → Object[] 很大 → 某个 List 不断 add
SessionImpl → 大量属性对象 → Session 没超时/没清理
3)Histogram(类直方图)
用它来发现”不该有这么多实例”的类:
操作:工具栏 → Histogram
列:
- Class Name: 类名
- Objects: 实例数量
- Shallow Heap: 实例自身大小(不含引用)
- Retained Heap: 实例 + 所有被引用对象的大小
关键操作:
1. 按 Retained Heap 排序 → 找占内存最多的类
2. 对嫌疑类右键 → List objects → with outgoing references
→ 看每个实例具体引用了什么
3. 对嫌疑类右键 → Merge Shortest Paths to GC Roots
→ 看这些实例的共同引用链 → 找到公共持有者
4. Compare Basket:对比两次 dump 的 Histogram
→ Objects 数量增长最大的类 = 泄漏嫌疑
Histogram 常见线索:
类名 可疑的信号
────────────────────────────────────────────────────────
HashMap$Node / LinkedHashMap$Entry 某个 Map/缓存一直在涨
byte[] / char[] 大量字符串/String 对象(看是谁引用的)
某业务对象数量 > 预期数量 × 100 泄漏
java.lang.ref.Finalizer 重写了 finalize() 的对象排队等回收
4)实战流程总结
拿到 .hprof 文件后:
1. Overview → Leak Suspects(2 分钟)
作用:快速定位最大嫌疑对象
如果自动分析给出明确答案 → 直接跳到第 4 步
2. Dominator Tree → 按 Retained 降序(5 分钟)
作用:找"谁支配了最多内存"
展开大节点,找不该大的对象
3. Histogram → 按 Retained 降序(3 分钟)
作用:找"哪个类的实例数/内存异常"
配合 List objects → with outgoing references
4. 对嫌疑对象 → Path To GC Roots → exclude weak refs(2 分钟)
作用:找到持有链 → 定位到代码行
引用链上第一个非框架代码 = 根因
延伸追问
- Q:Shallow Heap 和 Retained Heap 的区别?
→ Shallow = 对象自身占的内存(比如一个
ArrayList对象 24 bytes)。Retained = Shallow + 所有被它支配的对象的 Shallow 之和(即如果这个对象被回收,一共能释放多少内存)。分析泄漏看 Retained,不看 Shallow。 - Q:为什么分析时要 exclude weak references? → 弱引用不影响 GC——即使弱引用存在,对象也能被回收。不排除的话引用链会很嘈杂,可能显示一堆「被 WeakHashMap 引用」但实际没问题的大对象。
- Q:Compare Basket 怎么用? → 打开第一个 heap dump → Window → Compare Basket → 添加第二个 dump → 对比 Histogram。适合「刚重启」vs「跑了一段时间」的对比——确认谁在涨。
我的记法
- 三板斧:Leak Suspects(自动找)→ Dominator Tree(找支配者)→ Histogram(找异常类数量)
- 核心动作:右键 → Path To GC Roots(exclude weak refs)→ 找第一个业务代码
- 看 Retained,不只看 Shallow
- 一句话:「Dominator Tree 按 Retained 排,Path To GC Roots 找到持有链,第一个业务代码就是泄漏点」
状态
- 已背速记
- 能讲通俗版
- 能答追问