应优先在Retainers视图中定位闭包捕获的全局变量或长生命周期实例,确认对象是否真为死对象;筛选大Retained Size的Array/Object,检查Shallow Size与Retained Size差异,从底部向上分析Retainers路径锁定闭包源头,验证其是否被全局强引用未释放,并在Android WebView/Electron中关注ArrayBuffer等native内存泄漏。

直接看 Retainers 路径,重点不是找数组或对象本身,而是找它上面第一个“不该活那么久”的持有者——尤其是闭包捕获的全局变量或长生命周期实例。
确认目标对象是否真为“死对象”
先排除误判:该对象在业务逻辑中是否仍被合理使用?比如缓存、状态管理器、单例服务中的字段。若已卸载组件、关闭页面、退出流程,但对象仍在堆中且 Retained Size 持续不降,才视为可疑死对象。
- 在 Heap Snapshot 的 Summary 视图中筛选 Constructor 为
Array或Object,按 Retained Size 降序排列,挑出明显异常(如 >2MB)的实例 - 右键 → Reveal in Retainers view,确保展开完整路径,避免只看到顶层简化引用
- 检查该对象的 Shallow Size 是否极小(如几 KB),而 Retained Size 却达数 MB——这说明它拖着大量下游对象,是典型的“被意外保留”信号
从底部向上读 Retainers 路径,锁定闭包源头
Retainers 显示的是“谁让它活下来”,路径底部靠近 GC Root 的节点最危险。常见强根包括 Window、globalThis、未清除的 setTimeout 回调、绑定在全局元素上的 EventListener。
- 路径末尾若出现
[[Scopes]] → Closure → (array)或类似结构,说明该数组是被某个函数闭包捕获的局部变量 - 点开 Closure 节点的 Properties,查找是否有命名字段(如
cache、pendingRequests、historyBuffer)指向目标对象 - 继续上溯,看这个 Closure 是由哪个函数创建的——通常函数名会显示在路径中(如
initModule、setupPolling)。若该函数本应随页面销毁,却因定时器/监听器未清理而长期驻留,问题就在这里
验证闭包是否真被全局强引用
仅靠路径还不够,要确认这个闭包没有被显式释放。
- 在 Sources 面板中搜索该函数名,检查其调用位置:是否注册了全局事件(
document.addEventListener)、设置了未清除的定时器(setInterval)、或赋值给了window.xxx/globalThis.xxx - 若使用框架(如 React、Vue),检查对应组件是否遗漏
useEffect cleanup、beforeUnmount等生命周期钩子,导致闭包内变量无法释放 - 在 Console 中手动执行
delete window.xxx或clearInterval(id)后重新拍快照,观察该对象是否消失——可快速验证因果关系
Android WebView / Electron 场景需额外注意
这类环境里,闭包若持有 ArrayBuffer 或 TypedArray,其 Retained Size 可能隐含 native 内存,普通 JS 堆分析容易漏掉。
- 配合 Chrome DevTools 的 Allocation instrumentation on timeline 录制操作过程,观察内存增长是否与某次闭包执行强相关
- 在快照中筛选
ArrayBuffer构造函数,查看其 Retainers 是否也指向同一 Closure —— 若是,说明闭包同时锁住了 JS 堆和 native 堆 - 必要时用
adb shell dumpsys meminfo(Android)或 Electron 的process.memoryUsage()辅助交叉验证

















