支配树的核心是识别“谁真正持有对象、谁阻止对象被回收”,其根为GC Roots,每个节点的直接支配者是所有通往它的路径必经的最近对象,反映强可达性下的真实内存控制权。

分析 Heap Dump 中的支配树(Dominator Tree),核心是理解“谁真正持有对象、谁阻止了对象被回收”,而不是只看引用链长度或对象数量。它帮你快速定位内存泄漏的根因,比如某个本该被释放的缓存容器,却因为被一个静态字段意外强引用而活了下来。
什么是支配树?关键要懂“支配”关系
在支配树中,如果对象 A 是对象 B 的支配者(Dominator),意味着:从 GC Roots 到 B 的**每一条可达路径都必须经过 A**。换句话说,只要 A 被回收,B 就一定不可达(除非有其他非支配路径,但支配树已确保这是唯一入口)。所以树的根是 GC Root,每个节点只有一父节点(直接支配者),子节点是它“支配”的所有对象。
这和普通引用树完全不同——引用树可能发散、重复、包含弱/软引用;支配树是精简、唯一、强可达性的拓扑结构,反映真实的内存“控制权”。
用 Eclipse MAT 打开支配树视图
MAT(Memory Analyzer Tool)是最常用且对支配树支持最完善的工具:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 打开 .hprof 文件后,点击菜单 Reports → Dominator Tree,或直接点右上角“Dominator Tree”标签页
- 默认按“Retained Heap”降序排列——这个值最关键:表示如果删除该对象,**能立即释放多少堆内存**(即它支配的所有对象的 shallow heap 总和)
- 勾选右上角 “Group by package/class” 或 “Group by superclass”,快速聚焦可疑类型(如大量 HashMap、ArrayList、自定义 Service 类)
- 双击某行进入“Merge Shortest Paths to GC Roots”,查看它为什么还活着——这里显示的是**实际起作用的强引用链**(自动过滤掉虚/弱/软引用)
识别典型泄漏模式:看 retained heap + 引用链
支配树本身不告诉你“为什么错”,但结合上下文能快速锁定问题:
- 单个对象 retained heap 特别大(比如 >100MB):大概率是缓存未清理、日志堆积、大数组未释放。点开它,看它的 field 值(如 map.size、list.size)、所属类名、创建位置(如果有行号信息)
- 某个工具类或监听器实例出现在支配树靠前位置,retained heap 中等但数量多:检查是否注册了监听器没反注册、ThreadLocal 没 remove、匿名内部类持有了 Activity/Fragment(Android)或 ServletContext(Web)
- java.util.HashMap 或 ConcurrentHashMap 占主导,且 key 是业务对象:重点查 key 是否没实现 hashCode/equals、或 value 持有外部强引用导致整张 map 无法回收
- 线程对象(java.lang.Thread)retained heap 高:说明该线程还在运行,且它栈帧里的局部变量或 ThreadLocal 可能持有大量对象——看 “Thread Stack” 视图,找长生命周期对象
进阶技巧:用 OQL 定制查询 + 对比快照
支配树是静态快照,复杂问题需动态验证:
- 在 MAT 的 OQL Console 中执行:SELECT * FROM java.util.HashMap WHERE @retainedHeap > 5000000,直接筛选 retained heap 超 5MB 的 HashMap
- 用两个时间点的 dump(正常态 vs OOM 前)做 Compare Blame:MAT 会高亮 retained heap 增长最多的类和实例,比单看一棵树更准
- 导出支配树为 CSV(右键 → Export to CSV),用 Excel 筛选 “Class Name” 和 “Retained Heap”,加一列计算 “Retained per Instance” = Retained Heap / Objects,找出“单个实例吃内存”的罪魁

















