Retaining Path用于定位“死内存”,它展示从GC Roots到对象的引用链,揭示对象未被回收的原因;需通过“Shortest path to GC roots”追踪,重点分析首个非系统级节点(如闭包)及其捕获的变量(如this、cacheMap),避免误判debounce等无害闭包。

路径检索(Retaining Path)是堆快照分析中定位“死内存”的核心手段——它不告诉你对象在哪被创建,而是明确展示“为什么它还活着”:即从 GC Roots 到该对象之间所有阻止其被回收的引用链。当一个本该释放的对象因被全局闭包意外捕获而滞留,路径检索能逐层展开这条“不该存在的牵连”,直指闭包持有者和被捕获变量。
明确目标对象:先锁定可疑的“活死人”
在 Chrome DevTools 的堆快照 Summary 或 Dominators 视图中,优先筛选以下特征的对象:
- 构造函数为 (closure),且 Retained Size 显著高于 Shallow Size(比如几 KB 的闭包拖着几 MB 内存)
- 属于已卸载组件、已关闭弹窗、已销毁模块的实例(如
UserProfileView、ModalInstance),但仍在堆中存活 - 数组或 Map 类型对象,size 很大但内容已无业务用途(如缓存表未清理、事件监听器未解绑)
用“Retaining Paths”反向追踪引用源头
选中目标对象 → 右键 → “Retaining Paths” → 选择 “Shortest path to GC roots”(最短路径)或 “All paths”(全路径,更稳妥):
- 路径顶部一定是某个 GC Root,常见的是
window、globalThis、setTimeout回调、document或console - 重点观察路径中第一个非系统级的中间节点:如果它是某个闭包(
(closure)),且该闭包位于全局作用域或长期存活对象(如单例类、路由守卫)内,大概率就是元凶 - 路径中若出现
[[Scopes]] → Closure → Outer → variableName结构,说明该闭包捕获了名为variableName的外层变量——点开这个变量,确认它是否指向一个本该销毁的大对象(如组件 this、DOM 节点、大数组)
验证闭包捕获内容:打开 Scope 面板看真实值
在 Retaining Paths 中点击那个可疑闭包 → 右侧自动显示其 Scope 面板:
- 展开
Closure下的[[Scopes]]→ 找到Closure类型作用域 → 查看其中列出的变量名和对应值 - 若某变量值是
Object或Array,且其 Retained Size 极高,再点进去看它的 retaining tree,确认是否包含已卸载的组件实例或 Detached DOM 节点 - 特别留意捕获的
this、ref、cacheMap、eventHandlers等典型泄漏载体
区分真凶与替罪羊:避开常见误判陷阱
不是所有出现在路径里的闭包都是问题根源:
-
lodash.debounce或React.useCallback返回的闭包本身无害,关键看它捕获的依赖项是否包含不该长期持有的对象 - 路径中若出现
window.xxxHandler,不要只盯着 handler 函数,要点开window→ 查看xxxHandler属性的值 → 进入其 closure scope → 找出它真正引用了谁 - 若路径终点是
setTimeout或setInterval,检查回调函数是否在定时器清除前就已捕获了大对象;未 clearTimeout 是高频原因










