用 Chrome DevTools 排查内存泄漏、内存膨胀与频繁垃圾回收
页面越来越慢、从打开起就很吃力、操作中反复出现停顿,可能对应不同的内存问题。Chrome 的任务管理器能给出总体线索,Performance 记录能展示变化趋势,而 Memory 面板可以进一步定位被保留的对象和分配内存的函数。有效排查需要把这些证据串起来,不能只凭一个较大的内存数字下结论。
本文翻译整理自 Kayce Basques 的 Fix memory problems,源页标注最后更新于 2024-11-06 UTC,本稿于 2026-10-09 核对。原文部分截图和 Timeline 术语来自较早界面;下面保留功能名称并说明定位方式,实际按钮位置及可用分析类型以所用 Chrome 版本为准。本次未打开业务页面进行性能采样,也未执行原文用于制造内存增长的演示代码。

先分清三种不同的问题
原文以 RAIL 性能模型为出发点:优化应围绕真实用户的体验,而不是单纯追求某个指标。内存问题经常会以用户能感知的方式出现:
- 内存泄漏:页面中的错误使不再需要的对象持续被保留,使用过程中占用逐渐增加,性能也可能越来越差。
- 内存膨胀:页面一直使用超过实现相同功能所需的内存,体验可能从一开始就不好。
- 频繁垃圾回收:不断创建和回收对象会让页面频繁停顿。垃圾回收由浏览器安排,相关暂停可能影响脚本执行与响应。
“太多内存”没有统一阈值。同一个页面在高端手机上很流畅,在低端设备上却可能崩溃。应了解用户常用设备,并在这些设备上验证页面,而不是以开发机的容量代替用户环境。
编者补充:占用上升只是一条调查线索。缓存预热、正常新增内容和正在处理的数据也会让内存增加;需要结合相同操作重复执行后的趋势、垃圾回收后的存活对象,以及对象是否仍有业务用途来判断泄漏。
第一站:Chrome 任务管理器
按 Shift + Esc,或从 Chrome 主菜单进入“更多工具 → 任务管理器”。在表头上右键,启用 JavaScript memory 列。原文建议一起观察以下两个指标:
| 指标 | 含义及检查方向 |
|---|---|
| Memory footprint | 操作系统层面的进程内存占用,包含 DOM 等原生资源。DOM 节点增长可能推动它上升;不能把所有增长都归因于 DOM。 |
| JavaScript memory | JavaScript 堆的信息。原文重点关注括号内的 live 值,即当前可达对象占用的内存;其增加可能来自新对象或已有对象变大。 |
任务管理器适合确认问题在哪个页面、是持续增长还是反复波动,以及操作前后有没有变化。它不会直接告诉你哪个函数或哪条引用导致了问题。
用 Performance 记录看内存变化
打开 DevTools 的 Performance 面板,启用 Memory 相关记录选项,再记录能够复现问题的操作。原文建议在记录开始和结束附近分别点击 Collect garbage,手动触发垃圾回收,便于比较回收后的基线。这是诊断手段,不是修复代码的方式。
原文用下面的代码演示持续增长。它有意向页面添加大量节点并保留大字符串,只用于阅读理解;本稿未执行,不能粘贴进实际业务页面或持续触发。
var x = [];
function grow() {
for (var i = 0; i < 10000; i++) {
document.body.appendChild(document.createElement('div'));
}
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每触发一次 grow(),就创建 10,000 个 div,同时把一个大字符串放进全局数组 x。原文把字符串长度概括为“一百万个字符”;严格按 JavaScript 的 join 语义,这里生成的是 999,999 个 x。这一差异不改变演示要说明的持续保留问题。
原文界面中的 HEAP 曲线表示 JavaScript 堆;计数器区域还能按 JS heap、documents、DOM nodes、listeners、GPU memory 等项目查看趋势。不同 Chrome 版本可能调整这些图表。取消某项复选框可以隐藏对应曲线,减少干扰。
在这个例子中,DOM 节点数应随每次点击呈阶梯式上升。原文截图中的第一次回落来自记录开始时点击 Collect garbage 按钮触发的强制垃圾回收;随后节点和字符串分配会使堆曲线出现尖峰。更有价值的是比较强制 GC 后的基线:如果相同操作反复执行后,回收后的堆或仍存活的节点持续增加,就值得进一步检查。这仍是线索,下一步要找到具体保留者。
用堆快照找出 detached DOM 树
从文档树移除一个节点,并不意味着它马上能被回收。如果 JavaScript 仍持有可达引用,节点及其相关子树就可能继续存活。原文把这类不在页面 DOM 树中、却仍被代码引用的节点称为 detached 节点;它们是常见的泄漏来源。
下面的原文示例创建一棵没有插入页面的树,然后用全局变量保留它:
var detachedTree;
function create() {
var ul = document.createElement('ul');
for (var i = 0; i < 10; i++) {
var li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);
代码需要页面存在 id="create" 的元素。触发后生成一个 ul 和 10 个 li;它们不在当前文档树中,但 detachedTree 引用了 ul。示例的重点是这条引用,而不是“看不见的节点一定都有问题”。某些离屏内容可能是应用有意保留的。
- 进入 Memory 面板,选择 Heap snapshot,点击 Take snapshot。
- 等待快照生成和加载,再从左侧列表选中它。
- 在 Class filter 中输入
Detached,查看脱离文档的树。 - 展开节点,选择具体对象,在对象详情和引用保留信息中追踪谁仍然持有它。
在原文例子里,应沿引用追到 detachedTree。修复方向是在不再需要这棵树时释放业务持有的引用,并检查是否还有其他容器、闭包或监听关系保留了它。仅仅再次把节点从 DOM 中移除,并不能消除 JavaScript 侧已有的引用。
分配时间线:找到某段操作创建的对象
当你知道增长大约发生在哪个操作时,可以进一步记录分配时间线。原文用更小的例子只保留字符串分配:
var x = [];
function grow() {
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每次点击都会向全局数组添加另一个大字符串;它同样是有意制造持续内存保留的例子,本次没有运行。实际排查应记录自己的可复现操作。
在 Memory 面板选择分配时间线类型(原文称 Allocations on timeline,不同版本也可能使用 Allocation instrumentation on timeline),开始记录,执行怀疑导致泄漏的操作,随后停止。原文的蓝色柱形表示记录中的新增分配候选;选取某段时间后,构造函数列表会收窄到该时段分配的对象。
展开对象,再点击具体值查看详情。原文示例可以追到 Window 作用域中的 x。重要的不是“这里有分配”,而是这些分配在不需要之后是否仍然存活,以及引用链为什么没有断开。分配本身是正常程序行为。
分配采样:按函数找热点
Allocation sampling 适合从 JavaScript 函数的角度查看内存分配。在 Memory 面板选择该类型;如果页面有 worker,可在 Select JavaScript VM instance 中选择所需的 JavaScript 虚拟机实例作为分析目标。
点击 Start,执行需要调查的操作,再点击 Stop。原文的默认视图是 Heavy (Bottom Up),分配最多内存的函数位于上方。它能帮助你优先检查分配热点,但分配最多的函数不一定就是泄漏根因:对象可能由另一处代码长期保留。
直接观察被 JavaScript 保留的 detached 元素
原文还介绍 Detached elements 分析类型,用于列出因 JavaScript 引用而持续存在的 detached 元素,包括具体 HTML 节点及数量。它与堆快照的引用分析可以互相补充。如果当前版本没有相同入口,可使用堆快照中的 Detached 过滤与保留链继续调查;本文没有验证各 Chrome 版本的入口可用性。
频繁垃圾回收与真正的泄漏要分开处理
如果页面反复暂停,可以在任务管理器里观察 Memory 或 JavaScript Memory 是否频繁升降,也可以在 Performance 记录里观察堆曲线的锯齿式变化。原文以这些模式提示频繁垃圾回收,再通过分配时间线定位对象从哪里产生。
观察到反复回落,可能意味着对象能够回收、但创建速度或数量过大;这与“对象一直无法释放”是不同的问题。应把内存变化与操作时间、停顿和分配热点对应起来,而不是把所有锯齿曲线都标成泄漏。
编者补充:完成修复后,应使用相同设备、相同操作和相同记录方法复查,确认回收后的存活对象与用户体验都符合预期。堆快照可能包含页面中的文本、对象和敏感业务数据,分享前需要检查其内容。本次交付只进行了源文和示例的静态审核,不包含任何项目已经修复或性能提升的结论。











暂无评论内容