支配者树是分析工具基于引用关系构建的逻辑视图,用于识别释放某对象后将变得不可达的对象集合;其根为gc root,节点为必经的最近支配者,核心指标是retained size。

什么是支配者树(Dominator Tree)?
支配者树不是堆快照的原始数据,而是分析工具(如 Chrome DevTools、Eclipse MAT、VisualVM)基于对象引用关系**重构出的逻辑视图**,用于回答一个关键问题:“如果我释放这个对象,有多少其他对象会随之不可达?”支配者(dominator)指在所有从 GC Roots 到某对象的路径中,**必经的最近那个对象**。根节点永远是 GC Root,叶子节点通常是被某个大对象“独占持有”的小对象。
它对定位内存泄漏极有用:真正的泄漏源头往往不是体积最大的对象,而是**支配大量内存却本不该长期存活的对象**——比如一个本该被销毁的 Vue 组件实例,意外持有了整个 Map 缓存和一堆 ArrayBuffer。
Chrome DevTools 中打开并读懂支配者树视图
在 Memory 面板录制堆快照后,左上角切换到 Dominator view(不是 Summary 或 Containment)。默认按 Retained Size 降序排列,这就是你要盯住的核心列。
- 别只看
Size(自身占用),重点看Retained Size(它及其所有支配子树的总内存) - 展开某行时,子节点是它直接支配的对象,不是它的字段;子节点的
Retained Size一定 ≤ 父节点 - 右键点击任意对象 →
Reveal in Summary view,能跳转到传统引用链,验证为什么它没被回收 - 若看到大量
system / Context或compiled code占高位,通常说明闭包或 eval 动态代码持有引用,需检查function对象的scope链
常见误判陷阱:哪些“高 Retained Size”其实不危险?
Retained Size 高 ≠ 泄漏源。以下情况需人工过滤:
-
Window或Document对象天然支配巨量内存,属于正常生命周期管理,除非你发现它们支配了本不该存在的业务类实例(如MyChartManager) - 第三方库的缓存容器(如
Lodash.memoize返回的函数、axios的defaults.adapter内部 Map)可能 Retained Size 很高,但只要缓存策略合理且可驱逐,不算泄漏 - 单例模式对象(如
AppStore、Router)支配内存多是设计使然,关键看它是否意外持有了已卸载组件的ref或event listeners - DevTools 自身注入的调试对象(如
console.log输出的未清理引用)会在快照中残留,建议用console.clear()后再拍快照
如何从支配者树反向追踪到具体代码位置?
找到可疑支配者后,不能只停留在对象类型层面,必须定位到 JS 源码行:
- 在
Dominator view中双击对象,右侧打开Object Properties,找__proto__或构造函数名(如class MyService),确认是不是你的业务类 - 右键 →
Copy object ID,切到Summary view→ 粘贴 ID 搜索,进入后点Retainers标签页,看谁在引用它;反复向上点,直到出现Closure或Function类型的 retainers - 对
Functionretainers,点开看Scope,里面常有Closure条目,展开就能看到闭包捕获的变量名(如dataCache、mountedRef) - 若看到
EventListener,右键 →Reveal in Sources,DevTools 会直接跳转到绑定事件的那行 JS(前提是 sourcemap 正确且未压缩)
真正难的不是找到支配者,而是判断它为何没被释放——比如一个 setTimeout 回调里闭包持有了组件实例,而组件卸载后定时器未清除。这种逻辑耦合不会直接写在支配者树里,得靠 Retainers 链+代码上下文交叉验证。










