- 文档
- 教程
【免费下载链接】CN-Chrome-DevTools
Chrome开发者工具中文手册
本文基于 Chrome 开发者工具中文手册(CN-Chrome-DevTools 仓库)中 JavaScript 内存分析 文档展开,系统讲解 JavaScript Web 应用的内存分析方法论:如何用 Timeline 内存视图确认内存持续增长、如何理解 Shallow Size 与 Retained Size、如何用 Heap Profiler 的四种视图(Summary / Comparison / Containment / Dominators)与对象分配跟踪器逐一定位泄漏源。读完后,你将掌握一套完整的"先证明泄漏存在、再定位保留路径"的内存排查流程,并能读懂 V8 堆快照中的对象组、闭包与游离 DOM 树。
适用前提说明:本文档描述的是 Chrome DevTools 经典版本(Profiles / Timeline / Profiler 面板并存的时代,部分特性当时仅 Chrome Canary 可用)。现代 Chrome 已将堆快照整合进 Memory 面板、将 Timeline 升级为 Performance 面板,但 Shallow Size、Retained Size、支配树、游离节点等核心概念与排查方法至今不变,文中的术语对照与操作思路依然适用。
一、遇到内存问题先回答三个问题
内存泄漏是指计算机可用内存的逐渐减少:当程序持续无法释放其使用的临时内存时就会发生。JavaScript Web 应用同样会遇到原生应用中的泄漏和溢出问题,还要额外应对垃圾回收(GC)停顿。尽管 JavaScript 使用垃圾回收进行自动内存管理,但有效的内存管理依然很重要。
总体来说,当你觉得自己遇到了内存泄漏问题时,需要先回答三个问题:
我的页面是否占用了过多的内存?Timeline 内存视图(Memory view)和 Chrome 任务管理器能帮你确认是否使用了过多的内存。Memory view 能跟踪页面渲染过程中的DOM 节点计数、documents 文档计数和 JS 事件监听器计数。经验法则:避免对不再需要的 DOM 元素持有引用、移除不再需要的事件监听器、存储可能用不到的一大块数据时要格外留意。
我的页面有没有内存泄漏?对象分配跟踪器(Object allocation tracker)通过实时查看 JS 对象的分配来帮你定位泄漏;堆分析仪(Heap Profiler)则生成 JS 堆快照,通过分析内存图和比较快照之间的差异,找出没有被垃圾回收清理掉的对象。
我的页面垃圾强制回收有多频繁?如果页面 GC 很频繁,说明页面可能内存分配太频繁了。Timeline 内存视图能帮你发现感兴趣的停顿。
这三个问题构成了一条清晰的诊断主线:任务管理器粗筛 → Timeline 内存视图定量证明 → Heap Profiler 快照对比定位 → 对象分配跟踪器追踪分配点。后文的工具介绍正是沿着这条主线展开。
二、内存分析的核心术语
本节介绍内存分析时的常用术语,这些术语在 Java、.NET 等其他语言的内存分析工具中同样适用,也是堆分析仪 UI 和相关文档的基础。如果你用过其他语言的内存分析工具,这一节相当于复习。
2.1 对象大小:Shallow Size 与 Retained Size
把内存想象成一个包含基本类型(数字、字符串)和对象(关联数组)的图,由一系列相关联的节点组成。一个对象有两种使用内存的方式:
- 对象自身直接使用内存;
- 隐含地保持对其他对象的引用,这种方式会阻止垃圾回收(GC)对那些对象的自动回收。
使用堆分析仪分析内存问题时,会看到两个关键栏位:直接占用内存(Shallow Size)和占用总内存(Retained Size),两者的含义如下。
直接占用内存(Shallow Size,不包括被引用对象占用的内存)
这是对象本身占用的内存。典型的 JavaScript 对象都会保留内存用来描述该对象本身并存储它的直接值。一般来说,只有数组和字符串会有明显的直接占用内存。但字符串和数组常常在渲染器内存中存储主要数据部分,仅仅在 JavaScript 对象栈中暴露一个很小的包装对象。
渲染器内存指你分析的页面在渲染过程中所用到的所有内存:页面本身的内存 + 页面中的 JS 堆所用内存 + 页面触发的工作进程(workers)中的 JS 堆所用内存。然而,通过阻止垃圾自动回收别的对象,一个小对象也有可能间接占用大量内存。
占用总内存(Retained Size,包括被引用对象占用的内存)
一个对象一旦被删除,它所引用的依赖对象就无法再被GC 根(GC root)遍历到,它们占用的内存就会被释放——这个对象 Retained Size 就包括这些依赖对象占用的内存。
GC 根由*控制器(handles)*组成。这些控制器(不论局部还是全局)是在建立由内建函数(native code)到 V8 引擎之外的 JavaScript 对象的引用时创建的,都能在堆快照的GC roots > Handle scope和GC roots > Global handlers中找到。不深入了解浏览器实现原理的话,这些控制器看起来可能难以理解,但 GC 根和控制器你都不需要过多关心。
对应用开发者来说,真正重要的 GC 根有几种情况:
- Window 全局对象(所有 iframe 中的)。堆快照中有一个
distance字段,表示从 window 对象到达对应对象的最短路径长度; - 文档 DOM 树:由所有 document 可遍历到的 DOM 节点组成。不是所有节点都会被对应的 JS 引用,但有 JS 引用的节点在 document 存在的情况下都会被保留;
- 调试代码或 DevTools console 中创建的对象:例如 console 中某些代码执行结束后留下的对象。
注意:推荐在创建堆快照时,不要在 console 中执行代码,也不要启用调试断点——这些都会引入额外的保留引用,干扰分析结果。
内存图由一个根部开始,可能是浏览器的window对象或 Node.js 的Global对象。这些根对象如何被内存回收不受用户控制。不能被 GC 根遍历到的对象都将被内存回收。另外,Shallow Size 与 Retained Size 字段中的数据都以字节表示。
2.2 对象的占用总内存树
堆是由各种互相关联的对象组成的网状结构。在图论中,这种结构被称为图(内存图),由*边缘(edges)连接的节点(nodes)*组成,且都贴有标签:
- 节点(Nodes,即对象):节点的标签名由创建它的*构造函数(constructor)*的名称确定;
- 边缘(Edges):标签名就是属性名。
在堆分析仪生成的快照中可以看到distance字段:对象到 GC 根的距离。如果同一类型的所有对象距离都一样,而有一小部分对象的距离却比较大,那很可能出现了需要调查的问题——这类"离群"对象往往是泄漏嫌疑对象。
2.3 支配对象(Dominators)
支配对象构成一棵树结构,因为每个对象都恰好有一个支配者。支配者可能不会直接引用它所支配的对象,所以支配树不是内存图的一棵生成树。举例说明支配关系的判定:
- 节点 1 支配节点 2;
- 节点 2 支配节点 3、4 和 6;
- 节点 3 支配节点 5;
- 节点 5 支配节点 8;
- 节点 6 支配节点 7。
判定准则是:如果对象 B 出现在每条从 GC 根到对象 A 的路径中,那么 B 就是 A 的支配对象。例如节点 #7 虽不是 #10 的直接引用者,但它出现在每一条从根到 #10 的路径上,因此 #7 支配 #10。支配树是快速定位"内存增长点"的关键视角,对应的 Dominators 视图详见后文。
2.4 V8 内存模型速览
分析内存时,了解 V8 JavaScript 虚拟机(V8 VM)中的这些概念,对读懂堆快照非常有帮助。
JavaScript 对象描述——V8 中有三个原始类型:
- 数字(Numbers,如 3.14159…)
- 布尔值(Booleans,true 或 false)
- 字符型(Strings,如 'Werner Heisenberg')
它们不会引用别的值,只可能是叶子节点或终止节点。
数字以两种方式之一被存储:
- 31 位整数直接值,称为小整数(SMI,small integers);
- 堆对象,引用为堆值——堆值用来存储不适合 SMI 形式的数据(如双精度数 doubles),或者当值需要被*打包(boxed)*时(例如给数字再设置属性)。
字符型数据以两种方式存储:
- VM 堆中;
- 外部的渲染器内存中——此时会创建一个包装对象用来访问存储位置。例如 Web 页面包存的脚本资源和其他内容,而不是直接复制到 VM 堆中。
新创建的 JavaScript 对象在 JavaScript 堆(VM 堆)上分配内存,由 V8 的垃圾回收器管理,只要还有一个强引用它们就会保留在内存中。
**本地对象(local objects)**是所有不在 JavaScript 堆中的对象。与堆对象不同,它们在生命周期中不会被 V8 GC 处理,只能通过 JavaScript 包装对象引用。
**连接字符串(cons strings)**是由一对字符串合并成的对象,表示合并后的结果,且只在有需要时才真正合并。比如连接a和b得到字符串(a, b)表示连接结果;之后再把这个结果与d连接,得到的其实是另一个连接字符串((a, b), d)。
**数组(Arrays)**是数字类型键的对象,在 V8 中被广泛用于存储大数据量数据,字典这种键-值对对象也用数组实现。一个典型的 JavaScript 对象可以通过两种数组类型之一存储:命名属性、数字化的元素。如果只有少量属性,它们会被直接存储在 JavaScript 对象本身中。
Map——一种描述对象类型和结构的对象。Map 被用来描述对象结构以实现对对象属性的快速访问。
对象组(object groups):每个本地对象组都由一组相互关联的对象组成。比如一个 DOM 子树——每个节点都能访问到它的父元素、下一个子元素和下一个兄弟元素,它们共同构成一个关联图。需要注意的是,本地元素没有在 JavaScript 堆中直接表现——这正是它们大小为 0 的原因,而它们的包装对象被创建了出来。每个包装对象都有一个到本地对象的引用,用来传递对这些本地对象的操作;这些本地对象也有到包装对象的引用。但这并不会造成无法回收的循环:GC 足够智能,能分辨出已经没有引用包装对象的本地对象并释放它们。但如果有一个包装对象没有被释放,它将会保留整个对象组和相关的包装对象——这是 DOM 泄漏的典型根源。
三、先决条件与实用工具
3.1 Chrome 任务管理器:最粗粒度的内存观测
注意:使用 Chrome 做内存分析时,最好先搭建一个洁净的测试环境(关闭扩展、避免后台标签页干扰)。
打开 Chrome 任务管理器观察"内存"字段,然后在页面上做相关操作,可以很快定位某个操作是否导致页面占用大量内存。入口:Chrome 菜单 > 工具,或直接按Shift + Esc。打开后,在表头处右键勾选JavaScript 使用的内存这一列。
3.2 通过 Timeline 内存视图定位内存问题
解决问题的第一步是证明问题存在:需要创建一个可重现的测试作为问题的基准度量。没有可再现的程序,就不能可靠地度量问题——没有基准对比,就无法知道是哪些改变使问题出现。
**时间轴面板(Timeline panel)**对发现"程序什么时候出了问题"非常有帮助:它展示 Web 应用或站点加载与交互的每一个时刻,所有事件——从加载资源、解析 JavaScript、样式计算、垃圾回收停顿到页面重绘——都在时间轴上表示出来。
分析内存问题时,时间轴面板上的**内存视图(Memory view)**能用来观察四个指标:
- 使用的总内存——内存使用增长了么?
- DOM 节点数
- 文档(documents)数
- 注册的事件监听器(event listeners)数
证明一个问题的存在
首先找出你认为可能导致内存泄漏的动作——可以是发生在页面上的任何事件:鼠标移入、点击,或其他可能导致页面性能下降的交互。
操作流程:在时间轴面板开始记录(Ctrl + E或Cmd + E),然后执行你想要测试的动作;想强制进行垃圾回收时,点击面板上的垃圾筒图标。
下面给出两种典型判读方式:
- 如果反复测试后看到的是**锯齿状(sawtooth)**图形(内存视图的上方曲线),说明程序中有很多短时存在的对象——这是健康信号,不必担心;
- 如果一系列动作后内存没有回落到起始范围、DOM 节点数没有返回开始时的数目,则可以怀疑存在内存泄漏。某些点没有被垃圾回收的情形下,强制 GC 后曲线仍会留下"台阶"。
一旦确定内存上存在异常,就可以使用**分析面板(Profiles panel)上的堆分析仪(heap profiler)**来定位问题来源。
四、堆分析仪(Heap Profiler)操作详解
4.1 拍一个快照
在 Profiles 面板中选择Take Heap Snapshot,点击Start(或按 Cmd + E / Ctrl + E)开始,操作完成后停止即可得到快照。
几个关键机制:
- 快照最初保存在渲染器进程内存中,被按需导入到 DevTools;点击快照后才能看到它;
- 快照载入 DevTools 显示后,快照标题下面的数字显示了能够被引用到的(reachable)JavaScript 对象占有内存的总数;
- 拍快照时 GC 是自动执行的;在时间轴中点击垃圾桶按钮也可以随时手动强制执行垃圾回收。
4.2 清除快照
点击Clear all按钮图标即可清除所有快照。
注意:关闭 DevTools 窗口并不能从渲染器内存中删除已收集的快照——重新打开 DevTools 后,之前的快照列表依然存在。必须显式执行 Clear all。
4.3 快照的三种默认视图与可选视图
一个快照可以根据不同的任务切换视图,通过快照标题旁的下拉选择框切换。三种默认视图:
- Summary(概要)——通过构造函数名分类显示对象;
- Comparison(对照)——显示两个快照间对象的差异;
- Containment(控制)——可用来探测堆内容。
**Dominators(支配者)**视图需要额外开启——在 Settings 面板中启用Show advanced heap snapshot properties(在当时的 Chrome Canary 中开启后需重启 DevTools 生效),用于展示支配者树,是找到内存增长点(accumulation points)的利器。
4.4 通过颜色区分对象属性类型
对象的属性和属性值有不同的类型,并在界面上自动用颜色区分。每个属性是以下四种之一:
- a:property——通过名称索引的普通属性,由
.(点)操作符或[](中括号)引用,如["foo bar"]; - 0:element——通过数字索引的普通属性,由
[]引用; - a:context var——函数内的属性,在函数上下文(闭包)内,通过名称引用;
- a:system prop——由 JavaScript VM 添加的属性,JavaScript 代码不能访问。
命名为System的对象没有对应的 JavaScript 类型,它们是 JavaScript VM 的系统内置对象。V8 将大多数内置对象和用户 JS 对象放在同一个堆中,但它们只是 V8 的内部对象,看到时无需紧张。
五、四种视图详解
5.1 Summary 视图(概要视图)
打开快照时默认以概要视图显示,展示对象总数,可展开查看实例。第一层级是"总体"行,各列含义:
- Constructor(构造函数):表示所有通过该构造函数生成的对象;
- Objects Count:对象的实例数;
- Shallow size:由对应构造函数生成的对象的直接占用内存(Shallow size)总数;
- Retained size:对应对象占用的最大内存;
- Distance:对象到达 GC 根的最短距离。
展开一个总体行后会显示所有对象实例,每个实例的 Shallow Size 和 Retained Size 都被相应显示。@ 符号后的数字是对象的唯一 ID,有了它就可以逐个对象在不同快照间作对比。
记忆口诀:黄色的对象被 JavaScript 引用,而红色的对象是由黄色背景引用、被分离(detached)出来的 DOM 节点——红色游离树正是 DOM 泄漏的典型信号。
5.2 Comparison 视图(对照视图)
该视图用来对照不同快照、找到快照之间的差异,从而发现有内存泄漏的对象。要证明应用的某个操作没有造成泄漏(例如"打开一个 document 再关闭"这样成对的动作本不应泄漏),标准步骤是:
- 在操作前拍一个堆快照;
- 执行一个操作(做你认为会造成泄漏的动作);
- 撤销之前的操作(执行相反的操作,多重复几次);
- 拍第二个快照,将视图切换成对照视图,与快照 1 进行对比。
在对照视图下,两个快照之间的不同就会展现出来。展开一个总类目后,增加和删除的对象都清晰可见——如果撤销操作后某些对象只增不减,它们就是泄漏嫌疑对象。
5.3 Containment 视图(控制视图)
Containment 视图可以称作应用对象结构的"鸟瞰视图"。它能让你查看 function 内部、像观察 JavaScript 对象一样观察 VM 内部对象,从而看清应用非常低层的内存使用情况。该视图提供几个入口:
- DOMWindow 对象——JavaScript 代码的"全局"对象;
- GC 根——VM 垃圾回收器真正的 GC 根;
- Native 对象——浏览器对象"推入"JavaScript 虚拟机中自动操作的实体,如 DOM 节点、CSS 规则(下一节详细介绍)。
关于闭包的建议:给函数命名对你在快照中区分闭包函数非常有帮助。对比下面两例——未命名函数与命名函数:
// 函数表达式未命名:快照中只能看到匿名的闭包函数 function createLargeClosure() { var largeStr = new Array(1000000).join('x'); var lC = function() { // this is NOT a named function return largeStr; }; return lC; }// 函数表达式命名为 lC:快照中可通过名字区分不同闭包 function createLargeClosure() { var largeStr = new Array(1000000).join('x'); var lC = function lC() { // this IS a named function return largeStr; }; return lC; }命名后的函数在堆快照中会以lC这样的名字呈现,排查闭包保留链时定位困难会小很多。
5.4 揭露 DOM 内存泄漏
堆分析仪一个独特的能力是展示浏览器原生对象(DOM 节点、CSS 规则)与 JavaScript 对象之间的双向引用,这能帮助你发现因忘记解除引用游离 DOM 子节点而导致的难以发觉的内存泄漏。
DOM 内存泄漏可能超出你的想象。看下面这个例子——#tree对象什么时候能被 GC:
var select = document.querySelector; var treeRef = select("#tree"); var leafRef = select("#leaf"); var body = select("body"); body.removeChild(treeRef); //#tree can't be GC yet due to treeRef treeRef = null; //#tree can't be GC yet due to indirect //reference from leafRef leafRef = null; //#NOW can be #tree GC关键在于:#leaf通过parentNode属性递归引用到了#tree,因此只有当leafRef被置 null 后,#tree代表的整棵 DOM 树才会被 GC 回收——即使你早已removeChild并清空了treeRef。这就是"间接引用延长整棵游离树寿命"的典型场景。
排查建议:
- 在 Summary 视图中按 Retained Size 排序,找大块的游离 DOM 树;
- 在 Containment 视图中从 DOMWindow 或 GC 根进入,找到引用该树的黄色节点(有 JS 直接引用);
- 沿着从 DOM window 到该节点的属性引用链(如
window.foo.bar[2].baz)定位到代码中的具体引用。
原生对象在 Summary 和 Containment 视图中更容易找到——它们有专门的类目(如HTMLDivElement、HTMLAnchorElement、DocumentFragment),对应你代码中对 elements 或 document 对象的引用。
5.5 Dominators 视图(支配者视图)
支配者视图显示堆图的支配者树。它和 Containment 视图很像,但没有属性名——因为支配者可能是没有直接引用的对象(支配树不是堆图的生成树)。但这是一个非常有用的视图,能帮助我们快速定位内存增长点:如果某棵支配子树的 Retained Size 随着操作不断增大,树根对应的对象就是"积累点"(accumulation point)。
六、对象分配跟踪器(Object Allocation Tracker)
对象跟踪器整合了 Heap Profiler 的快照增量更新分析和 Timeline 面板的记录能力。跟其它工具一样,记录对象堆分配的流程是:启动记录 → 执行一系列操作 → 停止记录 → 分析。
对象跟踪器不间断地记录堆快照(频率达到每 50 毫秒一次!),结束时记录最后一个快照。堆分配分析器显示对象在哪里被创建,并定位它的保留路径。
开启并使用对象分析器:
- 确认你使用的是最新版的 Chrome Canary(该特性当时为 Canary 独有);
- 打开 DevTools 并点击齿轮图标(Settings);
- 打开 Profiler 面板,就能看到 "Record Heap Allocations" 选项。
如何读图:时间轴上的柱条表示在堆中生成的新对象——柱条高度对应对象的大小,颜色表示该对象是否在最后一个快照中仍然存在:
- 蓝色柱:timeline 最后该对象还在(存活到记录结束);
- 灰色柱:该对象在 timeline 中生成,但结束前已被内存回收。
典型排查场景:某个动作执行了 10 次,同一程序只保留了 5 个对象,于是最后留下 5 个蓝色柱条。对这些"本不该存活"的对象,你可以用 timeline 上的滑动条缩小到特定时刻的快照,找到那个时刻分配的对象;点击堆中的对象,下方会显示它的保留总内存树——检查这棵树通常能给出足够的信息,让你明白为什么这个对象没有被回收,从而对代码做相应修改去掉不必要的引用。
七、内存分析 FAQ
以下是原文档整理的常见问题,都是实战中反复被问到的细节。
问:我不能看到对象的所有属性,也看不到它们的非字符串值!为什么?
并非所有属性都完整地保存在 JavaScript 堆中。有些属性是通过执行原生代码的 getters 方法获取的。这些属性没有在堆快照中捕获,是为了防止对 getters 方法的调用和避免程序状态的改变(如果这些 getters 不是"纯"函数,调用它们可能产生副作用)。同样,非字符串的值(如数字)没有被捕获是为了减少快照的大小。
问:@ 符号后面的数字是什么意思——是地址还是 ID?这个 ID 值真的是唯一的么?
这是对象 ID。显示对象地址没有意义,因为对象会在垃圾回收时被移除。这些 ID 是真正的 ID——即它们在不同的快照间是唯一表示的,这样就可以对堆状态进行精确对比。维持这些 ID 会给 GC 流程增加额外开销,但仅在记录第一个堆快照时分配——如果堆分析仪没被用到,就不会有额外开销。
问:"死"(无法引用到的)对象被包含在快照中了么?
没有,只有可引用到的对象才会显示在快照中。而且,拍快照前都会先自动执行 GC 操作(防止堆尺寸虚高)。垃圾对象依然显示在快照之外。
问:GC 根是由什么组成的?
由很多部分组成:
- 原生对象图(native object graph);
- 符号表(symbol table);
- VM 线程中的栈(stack);
- 编辑缓存(editing cache);
- 控制器上下文(controller contexts);
- 全局控制器(global controller)。
问:Heap Profiler 和 Timeline Memory view 都能检测内存泄漏,应该先用哪个?
先 Timeline。Timeline 面板是你第一次发现页面变慢时用来论断"是否内存使用过多"的工具。网站变慢是内存泄漏的典型信号,但也可能是其它原因——可能是渲染或网络传输方面的瓶颈,所以要确保解决的是网页的真正问题。论断流程:打开 Timeline 面板和 Memory 标签 → 点击 record → 在应用上重复几次你认为可能导致泄漏的操作 → 停止记录。如果内存使用图一直增长而没有相应下降,说明应用可能有内存泄漏。
正常应用的内存使用图形是锯齿状的——内存使用后被 GC 回收,这种锯齿不用担心(甚至一个空的requestAnimationFrame也会造成锯齿,这是不可避免的)。需要警惕的是"增长线"——内存分配了却持续保留的斜线。诊断时 Memory 标签中的DOM node counter、Document counter 和 Event listener count也很有用:DOM 节点数变化对应的是原生内存,不会反映在 JavaScript 内存图中,所以三条曲线要结合起来看。一旦确认存在泄漏,再用堆分析仪找到泄漏的具体位置。
问:堆快照中有的 DOM 节点标记为红色"Detached DOM tree",其它是黄色,这是什么意思?
- 红色节点(深色背景):没有从 JavaScript 到它们的直接引用,但它们是分离出来的 DOM 结构的一部分,所以仍在内存中保留。可能有一个节点被 JavaScript 引用到了(可能在闭包中或某个变量里),这个引用会阻止整棵 DOM 树被回收。
- 黄色节点(黄色背景):有 JavaScript 的直接引用。在同一个分离的 DOM 树中找到黄色节点来定位你的 JavaScript 引用——沿着引用链(如
window.foo.bar[2].baz)就能看到是谁保留了整棵树。
问:Shallow Size 和 Retained Size 分别代表什么?区别是什么?
对象可以在内存中以两种方式"活着":直接被另一个可访问(alive)对象保留(window 和 document 对象总是可访问的),或被原生对象(如 DOM 对象)隐含地保留引用。后一种方式会阻止对象被 GC 自动回收,是导致内存泄漏的可能。对象自身占用的内存称为直接占用内存(一般数组和字符串的 shallow size 较大)。
问:constructor 和 retained 字段下有很多数据,应该从哪开始调查是否遇到了内存泄漏?
一般最好从通过 retainers 排序的第一个对象开始,retainers 之间通过距离(到 window 对象的距离)排序。距离最短的对象可能是首选的泄漏嫌疑对象——它意味着保留链离全局作用域最近、最"扎眼",通常也最容易在代码中对应到具体的变量。
问:Summary、Comparison、Dominators 和 Containment 这些视图之间的不同是什么?
- Summary(概要)视图:通过构造函数分组寻找对象(和对象的内存使用),对找出 DOM 内存泄漏很有帮助;
- Comparison(对照)视图:通过显示哪些对象内存被正确回收来搜寻泄漏。通常在一个操作前后记录两个(或更多)快照,通过察看释放的内存和引用数目的差值来确认是否有泄漏并找到原因;
- Containment(控制)视图:对对象结构有更好的展示,帮助分析全局作用域(如 window)中对象的引用情况,能深入闭包并钻到对象更深层查看;
- Dominators(支配者)视图:帮助确认没有多余的对象还挂在某个位置(如被引用的状态),确认对象的删除/垃圾回收真正起了作用,快速定位积累点。
问:堆分析仪中 constructor(一组)内容里的特殊项代表什么?
- (global property)——全局对象(如
window)和引用它的对象之间的中间对象。如果一个对象由构造函数 Person 生成并被全局对象引用,引用路径就是[global] > (global property) > Person。这跟一般的直接引用不同:引入中间对象有性能方面的原因——全局对象改变很频繁,非全局变量的属性访问优化对全局变量并不适用。 - (roots)——保留树视图中 roots 的条目是"持有对所选对象引用的实体",也包括引擎为自身目的创建的引用。引擎有引用对象的缓存,但这类引用都是弱引用,不会阻止对象被回收。
- (closure)——函数闭包中一组对象引用的引用。
- (array, string, number, regexp)——引用了 Array、String、Number 或正则表达式对象类型的属性组。
- (compiled code)——与编译代码相关的一切。Script 类似函数,但对应
<script>的内容;SharedFunctionInfos(SFI)是函数与编译代码之间的对象——函数通常有上下文(context),而 SFI 没有。 - HTMLDivElement、HTMLAnchorElement、DocumentFragment等——你代码中对 elements 或 document 对象的引用。
程序生命周期中生成的很多其它对象(包括事件监听器或自定义对象)可以在下面的 controllers 类目中找到。
问:做内存分析时需要关闭 Chrome 里可能产生影响的什么功能?
建议使用所有扩展功能都关闭的隐身模式做内存分析,或者指定一个全新的用户数据目录(--user-data-dir="")后打开 Chrome。应用、扩展甚至 console 中的记录都会对分析产生潜在影响——想让分析结果可靠,就禁用它们。
八、诊断内存泄漏的帮助实例
在测试你自己的页面之前,强烈建议先在一组官方 DOM 节点内存泄漏练习例子上做试验(示例来自官方文档对应的在线 demo 页,可按名称检索):
| 示例 | 练习目标 |
|---|---|
| Example 1: Growing memory | 通过 Timeline 内存视图观察内存持续增长 |
| Example 2: Garbage collection in action | 在 Timeline 中监控垃圾回收过程 |
| Example 3: Scattered objects | 用堆分析仪观察分散对象的集合 |
| Example 4: Detached nodes | 查看节点在 Timeline 中的生命周期,拍快照找分离节点 |
| Example 5: Memory and hidden classes | 理解隐藏类(hidden classes)与内存 |
| Example 6: Leaking DOM nodes | 了解 DOM 节点在哪里泄漏、如何定位 |
| Example 7: Eval is evil (almost always) | 分析 eval 产生的闭包对内存的影响 |
| Example 8: Recording heap allocations | 练习记录堆分配(对象分配跟踪器) |
| Example 9: DOM leaks bigger than expected | 理解为何 DOM 泄漏比预期更大(对象组) |
| Example 10: Retaining path | 分析保留路径与支配者 |
| Example 11: Last exercise | 综合练习 |
另有五组进阶练习:Gathering scattered objects(汇总视图)、Verifying action cleanness(对照视图验证操作无泄漏)、Exploring the heap contents(Containment 视图探索堆内容)、Uncovering DOM leaks(分离 DOM 树)、Finding accumulation points(Dominators 视图找积累点)。
九、社区资源与延伸阅读
社区贡献了很多用 Chrome DevTools 定位和解决 Web 应用内存问题的资料,值得收藏(按原文档推荐整理):
- Finding and debugging memory leaks with the Chrome DevTools(Gonzalo Ruiz de Villa 的幻灯片)
- JavaScript profiling with the Chrome DevTools(Zack Grossbart,Smashing Magazine)
- Effective memory management at GMail scale(HTML5 Rocks,理解"有效内存管理"的必读文章)
- Chrome DevTools Revolutions 2013(HTML5 Rocks)
- Rendering and memory profiling with the DevTools(Open Academy 演讲幻灯片)
- Performance optimization with DevTools timeline and profile(Addy Osmani)
在 CN-Chrome-DevTools 仓库中,与本文相邻的 Using the Timeline 和 Profiling JavaScript Performance 分别覆盖 Timeline 面板与 CPU 性能剖析,可与本指南配合使用:Timeline 发现"何时变慢",CPU Profile 排查"JS 执行瓶颈",Heap Profiler 排查"内存保留"。
写在最后
现代 JavaScript 引擎已经有很强的自动回收代码产生的内存垃圾的能力,但这只能做到这样了——应用仍然会因为逻辑错误(悬空引用、未解除的监听器、过大的缓存)而产生内存泄漏。使用相应的工具找到应用的瓶颈,并记住那条核心原则:不要靠猜——测试它(test it)。完整的排查闭环是:任务管理器粗筛 → Timeline 内存视图定量证明泄漏 → 堆快照 Summary/Comparison 视图锁定对象 → Containment/Dominators 视图读懂保留路径 → 修改代码并重复验证。
- 文档
- 教程
【免费下载链接】CN-Chrome-DevTools
Chrome开发者工具中文手册
相关推荐
Hivemind安全最佳实践:保护团队数据与技能资产
Hivemind安全最佳实践:保护团队数据与技能资产 Hivemind作为一款多智能体协作平台,其核心价值在于整合团队知识与技能资产。随着数据规模的增长,确保系
人工智能AI AgentAgent 记忆AI 技能RAGMCP 服务终极gridstack.js内存泄漏排查指南:Chrome DevTools内存面板实战教程
终极gridstack.js内存泄漏排查指南:Chrome DevTools内存面板实战教程 gridstack.js作为一款强大的网格布局库,在前端开发中广泛
前端UI组件decimal.js内存泄漏排查:Chrome DevTools实战指南
decimal.js内存泄漏排查:Chrome DevTools实战指南 引言:为什么高精度计算库也会内存泄漏? 在金融科技、科学计算等领域,JavaScrip
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考