如何用 Chrome DevTools 分析内存泄漏?
结论:用 Chrome DevTools 查内存泄漏,核心动作只有三步——先用 Performance 面板确认「JS 堆涨上去不回落」,再用 Memory 面板拍三张堆快照做 Comparison 对比锁定增长的对象,最后看 Retainers 引用链找到是谁没放手。整套流程熟练后 10 分钟能定位到具体代码行。
怎么判断页面到底有没有内存泄漏?
结论:只要在停止操作并手动 GC 后,JS Heap 曲线回不到基线,就是泄漏。
打开 DevTools(Windows/Linux Ctrl+Shift+I,macOS Cmd+Option+I),切到 Performance 面板,点右上角齿轮展开,勾上 Memory 复选框。然后点录制,做一段可重复的操作(比如反复打开再关闭同一个弹窗 10 次),停止录制。
看生成的曲线图,重点三条:JS Heap、Nodes、Documents。
- 正常情况:曲线是锯齿形,每次 GC 后回落到同一条基线附近。
- 泄漏情况:每次操作后锯齿的谷底都比上一次高一截,形成阶梯式上升。
- Nodes 和 Documents 只涨不跌,说明 DOM 节点被移除后仍被 JS 引用,即常见的分离 DOM(Detached DOM)——节点已经不在文档树里,但因为还有变量指着它,无法被回收。
如果三条线都平稳回落,那多半不是内存泄漏,只是缓存占得多,别浪费时间。
三张堆快照法具体怎么拍?
结论:拍 S1 → 重复操作 10 次并 GC → 拍 S2 → 再重复 10 次并 GC → 拍 S3,用 S3 对比 S1,Delta 为正且随操作次数线性增长的对象就是嫌疑犯。
切到 Memory 面板,选择 Heap snapshot,然后:
- 点快照按钮左边的垃圾桶图标 Collect garbage,强制 GC;
- 拍第一张快照 S1;
- 回到页面,重复泄漏嫌疑操作 10 次(不要多做别的);
- 再点垃圾桶 GC,拍 S2;
- 再重复 10 次同样的操作,GC,拍 S3。
在 S3 的视图下拉框里把 Summary 切成 Comparison,Base 选 S1。表格里的 # Delta 和 Size Delta 就是两次快照之间的净增。
判断标准很明确:重复了 20 次操作,某个构造函数如果增加了 20 个实例(或 40 个),基本可以确认泄漏;如果只增加 1~2 个,多半是 DevTools 自身的噪声,忽略。
找到嫌疑对象后,怎么定位到是哪行代码?
结论:在快照里选中对象,看下方 Retainers 面板(保留者引用链),从下往上读,第一个出现在你业务代码里的那一环就是源头。
Retainers 展示的是「谁引用了这个对象」,层级从深到浅。常见链路长这样:Detached HTMLDivElement ← someArray ← closure ← window.myCache,说明这个节点被挂到了全局缓存上。
两个提速技巧:
- 快照顶部搜索框直接搜
Detached,Chrome 会把所有分离 DOM 聚合显示,先看它们最省事。 - Console 里用
queryObjects(MyClass.prototype)可以直接列出当前存活的所有该原型实例数量,配合快照能快速验证是不是某个类没被回收。排查事件监听时用getEventListeners(window)或getEventListeners(document),能看到还挂着哪些监听器。
常见的内存泄漏有哪几类?
结论:前端 90% 的泄漏集中在六种模式,按出现频率排序依次是事件监听未移除、定时器未清理、闭包持有大对象、全局缓存无上限、观察者未 disconnect、Promise/异步链路持有上下文。
- 事件监听未移除:
window.addEventListener('resize', fn)用了但没removeEventListener,组件销毁后 fn 及其闭包里的 DOM 全都活着。 - 定时器未清理:
setInterval没在componentWillUnmount/useEffect的 cleanup 里clearInterval,每次挂载都叠一个。 - 观察者未断开:
MutationObserver、IntersectionObserver、ResizeObserver都必须调用disconnect()。 - 全局缓存无上限:
Map/ 数组做缓存只写不删,或挂在window上当全局变量用。 - 闭包持有大对象:回调里引用了整个大数组、大 JSON,导致整块内存无法释放。
- Detached DOM:DOM 从页面移除了,但 JS 变量还存着引用。
排查时有哪些坑必须避开?
结论:不用无痕窗口、不先 GC、单次会话拍太多快照,这三点会让结果完全失真。
- 用无痕窗口:扩展程序会注入脚本、持有 DOM,产生大量假阳性。
- 每张快照前必须 GC:不点垃圾桶直接拍,快照里塞满待回收垃圾,Delta 全是噪声。
- 别拍超过 4~5 张:快照本身会占用几十上百 MB 内存,拍太多反而拖垮页面,也会让相邻快照的对比失去意义。
- Allocation instrumentation on timeline 适合看「什么时候分配」:录制时蓝色竖条代表该时刻分配且仍存活的对象,灰色代表已回收。蓝条持续堆积就是泄漏信号,点蓝条能直接看到分配时的调用栈;缺点是开销大、会让页面变慢,只适合复现路径很短的场景。
把上面的流程走一遍:Performance 确认曲线不回落 → 三快照 Comparison 锁定增长对象 → Retainers 找到引用源头 → 对照六大模式改代码 → 再跑一次快照验证 Delta 归零。这套闭环是 Chrome DevTools 排查内存泄漏最可靠的路径,比凭感觉猜代码快得多。
原文链接:https://www.gj0.com/thread-324.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。