使用内存工具记录堆快照

使用 内存 工具中的堆探查器执行以下操作:

  • 记录 JavaScript 堆 (JS 堆) 快照。
  • 分析内存图。
  • 比较快照。
  • 查找内存泄漏。

DevTools 堆探查器显示 JavaScript 对象和呈现的网页上的相关 DOM 节点使用的内存分布。

采取快照

  1. 打开要分析的网页。 例如,在新窗口或选项卡中打开 “散点对象 ”演示页。

  2. 若要打开 DevTools,请右键单击网页,然后选择“ 检查”。 或者,按 Ctrl+Shift+I (Windows、Linux) 或 Command+Option+I (macOS) 。 DevTools 随即打开。

  3. 在 DevTools 的 “活动栏”上,选择“ 内存 ”选项卡。如果该选项卡不可见,请单击“ 更多工具 (更多工具”图标) 按钮。

  4. “选择分析类型”部分中,选择“堆快照”选项按钮。

  5. “选择 JavaScript VM 实例”下,选择要分析的 JavaScript VM。

  6. 单击“采取快照”按钮:

“内存”工具、“堆快照”选项处于选中状态,并突出显示了“采取快照”按钮

将新录制的堆快照加载到 DevTools 中并进行分析后,将显示快照,并且 HEAP SNAPSHOTS 下的“配置文件”边栏中会显示一个新条目:

可访问对象的总大小

新边栏项下面的数字显示可访问的 JavaScript 对象的总大小。 若要详细了解堆快照中的对象大小,请参阅内存术语中的对象大小和距离

快照仅显示内存图中可从全局对象访问的对象。 快照始终从垃圾回收开始。

采取另一个快照

若要在内存工具中显示另一个快照,请在边栏中单击现有快照上方的“配置文件”:

“配置文件”按钮,用于获取另一个快照

清除快照

若要从 内存 工具中清除所有快照,请单击“ 清除所有配置文件 (清除图标) 图标:

删除快照

查看快照

可以在 内存 工具中以多种不同方式查看堆快照。 在 UI 中查看堆快照的每种方式对应于不同的任务:

View 内容 用于
摘要 显示按构造函数名称分组的对象。 根据按构造函数名称分组的类型查找对象及其使用的内存。 有助于跟踪 DOM 泄漏。
比较 显示两个快照之间的差异。 比较操作前后的两个 (或更多) 内存快照。 检查释放内存中的增量和检查引用计数有助于确认内存泄漏的存在和原因,并有助于确定其原因。
遏制 允许浏览堆内容。 提供更好的对象结构视图,帮助分析全局命名空间 (窗口中引用的对象,) 找出哪些对象在周围保留对象。 使用它来分析闭包,并在低级别深入了解对象。

若要在视图之间切换,请使用 内存 工具顶部的下拉列表:

切换视图选择器

注意

并非所有属性都存储在 JavaScript 堆上。 不会捕获使用运行本机代码的 getter 实现的属性。 此外,不会捕获数字等非字符串值。

摘要视图

最初,堆快照在“摘要”视图中打开,其中显示构造函数列表:

摘要视图

可以展开列表中的每个构造函数以显示使用该构造函数实例化的对象。

对于列表中的每个构造函数, “摘要” 视图还显示一个数字,例如 ×123,指示使用构造函数创建的对象总数。 “ 摘要” 视图还显示以下列:

列名称 说明
距离 使用节点的最短简单路径显示到根的距离。 请参阅内存中的距离术语
浅层大小 显示由特定构造函数创建的所有对象的浅表大小之和。 浅层大小是对象直接持有的 JavaScript 堆的大小。 对象的浅表大小通常很小,因为 JavaScript 对象通常只将对象的描述(而不是值)存储在对象的直接保留内存中。 大多数 JavaScript 对象将其值存储在 JavaScript 堆中其他位置的 后备存储 中,并且仅在该对象直接拥有的 JavaScript 堆部分公开一个小包装器对象。 请参阅内存术语中的浅层大小
保留的大小 显示同一组对象之间的最大保留大小。 删除对象后可以释放的内存大小 (且依赖项不再可访问,) 称为保留的大小。 请参阅内存术语中的保留大小

“摘要” 视图中展开构造函数后,将显示所有构造函数的实例。 对于每个实例,浅表大小和保留大小显示在相应的列中。 字符后面的 @ 数字是对象的唯一 ID,允许你基于每个对象比较堆快照。

“摘要”视图中的构造函数条目

内存工具中的“摘要”视图列出了对象构造函数组:

构造函数组

“摘要”视图中的构造函数组可能是内置函数,例如 ArrayObject ,或者可能是在你自己的代码中定义的函数。

若要显示由给定构造函数实例化的对象列表,请展开构造函数组。

摘要视图中的特殊类别名称

摘要” 视图还包含不基于构造函数的特殊类别名称。 这些特殊类别包括:

类别名称 说明
(数组) 与 JavaScript 中可见的对象不直接对应的各种内部数组对象,例如 JavaScript 数组的内容或 JavaScript 对象的命名属性。
(编译的代码) V8 (Microsoft Edge 的 JavaScript 引擎) 的内部数据需要运行 JavaScript 或 WebAssembly 定义的函数。 V8 自动管理此类别中的内存使用情况:如果某个函数多次运行,V8 会为该函数使用更多内存,以便函数运行得更快。 如果某个函数在一段时间内未运行,V8 可能会删除该函数的内部数据。
(串联字符串) 当两个字符串串联在一起时(例如使用 JavaScript + 运算符时),V8 可能会选择在内部以 串联字符串的形式表示结果。 V8 创建指向这两个字符串的小对象,而不是将两个字符串的所有字符复制到新字符串中。
InternalNode 在 V8 外部分配的对象,例如由 Microsoft Edge 的呈现引擎 Blink 定义的 C++ 对象。
(对象形状) 有关对象的信息,例如它们具有的属性数以及对它们的原型的引用,V8 在创建和更新对象时会在内部维护这些对象。 这允许 V8 有效地表示具有相同属性的对象。
(切片字符串) 创建子字符串时(例如使用 JavaScript substring 方法时),V8 可能会选择创建 切片字符串 对象,而不是从原始字符串复制所有相关字符。 此新对象包含指向原始字符串的指针,并描述要使用的原始字符串中的字符范围。
system/ Context JavaScript 范围中的局部变量可由某些嵌套函数访问。 每个函数实例都包含一个内部指针,指向执行上下文,以便可以访问这些变量。
(系统) 尚未以任何更有意义的方式分类的各种内部对象。

比较视图

若要查找泄漏的对象,请比较多个快照。 在 Web 应用程序中,通常执行操作,然后反向操作不应导致内存中的更多对象。 例如,打开文档然后关闭文档时,内存中的对象数应与打开文档之前相同。

若要验证某些操作不会造成泄漏,请执行以下操作:

  1. 在执行操作之前,先将堆快照。

  2. 执行操作。 也就是说,以某种可能导致泄漏的方式与页面交互。

  3. 执行反向操作。 也就是说,执行相反的交互并重复几次。

  4. 获取第二个堆快照。

  5. 在第二个堆快照中,将视图更改为“比较”,并将其与快照 1 进行比较。

“比较” 视图中,将显示两个快照之间的差异:

比较视图

展开列表中的构造函数时,将显示已添加和已删除的对象实例。

包含视图

包含” 视图允许你查看函数关闭的内部,观察虚拟机 (VM) 构成 JavaScript 对象的内部对象,并了解应用程序在非常低的级别使用的内存量:

包含视图

包含” 视图显示以下类型的对象:

包含视图入口点 说明
DOMWindow 对象 JavaScript 代码的全局对象。
GC 根 JavaScript 虚拟机的垃圾回收器使用的 GC 根。 GC 根由内置对象映射、符号表、VM 线程堆栈、编译缓存、句柄范围和全局句柄组成。
本机对象 浏览器创建的对象(如 DOM 节点和 CSS 规则)显示在 JavaScript 虚拟机中以允许自动化。

“保留器”部分

“保留器”部分显示在“内存”工具的底部,并显示指向所选对象的所有对象。 在“摘要”、“包含”或“比较”视图中选择其他对象时,将更新“保留器”部分。

在以下屏幕截图中,在 “摘要” 视图中选择了一个字符串对象,“ 保留器 ”部分显示该字符串由 x 类实例的 Item 属性保留(在 example-03.js 文件中找到):

“保留器”部分

隐藏周期

“保留器 ”部分中,分析保留所选对象的对象时,可能会遇到 周期。 当同一对象在所选对象的保留器路径中多次出现时,会发生周期。 在 “保留器 ”部分中,通过灰显来指示已循环的对象。

为了帮助简化保留器路径,请在“ 保留器 ”部分中隐藏周期,方法是单击“ 筛选器边缘 ”下拉菜单,然后选择“ 隐藏循环”:

“保留器”部分中的“筛选器边缘”下拉菜单已选中“隐藏循环”

隐藏内部节点

内部节点 是特定于 V8 的对象, (Microsoft Edge) 中的 JavaScript 引擎。

若要在 “保留器 ”部分隐藏内部节点,请在 “筛选器边缘 ”下拉菜单中,选择“ 隐藏内部节点”。

将“浅表大小”列配置为包含整个对象的大小

默认情况下,“内存”工具中的“浅层大小”列仅包含对象本身的大小。 浅层大小是对象直接持有的 JavaScript 堆的大小。 对象的浅表大小通常很小,因为 JavaScript 对象通常只将对象的描述(而不是值)存储在对象的直接保留内存中。 大多数 JavaScript 对象将其值存储在 JavaScript 堆中其他位置的 后备存储 中,并且仅在该对象直接拥有的 JavaScript 堆部分公开一个小包装器对象。 例如,JavaScript Array 实例将数组的内容存储在后备存储中,该存储是数组的浅表大小中未包括的单独内存位置。

可以将“ 浅表大小” 列配置为报告对象的全部大小,包括对象的后备存储区的大小。

若要在“ 浅表大小 ”列中包括对象的完整大小:

  1. 在 DevTools 中,单击“ 自定义和控制 DevTools (自定义和控制 DevTools 图标) ”按钮,然后单击 “设置 (设置”图标) 。 或者,当 DevTools 具有焦点时,按 F1

  2. “试验 ”部分中,选中复选框“ 在堆快照中,将后备存储大小视为包含对象的一部分”。

  3. 单击“设置”页的“关闭 (x) ”按钮,然后单击“重新加载 DevTools”按钮。

  4. 使用新堆快照。 “ 浅表大小” 列现在包括对象的完整大小:

    堆的“浅表大小”列快照

按节点类型筛选堆快照

使用筛选器专注于堆快照的特定部分。 在内存工具中快照查看堆中的所有对象时,可能很难专注于特定对象或保留路径。

若要仅关注特定类型的节点,请使用右上角的 “节点类型” 筛选器。 例如,若要仅查看堆中的数组和字符串对象,快照:

  1. 若要打开 “节点类型 ”筛选器,请单击右上角的“ 默认值 ”。

  2. 选择 “数组 ”和“ 字符串” 条目。

    堆快照更新为仅显示数组和字符串对象:

    内存工具中堆快照中的节点类型

查找特定对象

若要在收集的堆中查找对象,可以使用 Ctrl+F 进行搜索并提供对象 ID。

发现 DOM 泄漏

内存工具能够显示浏览器本机对象 (DOM 节点、CSS 规则) 和 JavaScript 对象之间有时存在的双向依赖关系。 这有助于发现由于内存中被遗忘的分离 DOM 节点而发生的内存泄漏。

请考虑以下 DOM 树:

DOM 子树

下面的代码示例创建 JavaScript 变量 treeRefleafRef,它们引用树中的两个 DOM 节点:

// Get a reference to the #tree element.
const treeRef = document.querySelector("#tree");

// Get a reference to the #leaf element,
// which is a descendant of the #tree element.
const leafRef = document.querySelector("#leaf");

在以下代码示例中 <div id="tree"> ,元素从 DOM 树中删除:

// Remove the #tree element from the DOM.
document.body.removeChild(treeRef);

<div id="tree">无法对元素进行垃圾回收,因为 JavaScript 变量treeRef仍然存在。 变量 treeRef 直接引用 元素 <div id="tree"> 。 在以下代码示例中 treeRef , 变量为 null:

// Remove the treeRef variable.
treeRef = null;

元素 <div id="tree"> 仍无法进行垃圾回收,因为 JavaScript 变量 leafRef 仍然存在。 属性 leafRef.parentNode 引用 <div id="tree"> 元素。 在以下代码示例中 leafRef , 变量为 null:

// Remove the leafRef variable.
leafRef = null;

此时, <div id="tree"> 可以垃圾回收元素。 treeRefleafRef 必须首先为 null,才能对 元素下的<div id="tree">整个 DOM 树进行垃圾回收。

演示网页:示例 6:DOM 节点泄漏

若要了解 DOM 节点可能泄漏的位置以及如何检测此类泄漏,请打开示例网页示例 6:在新窗口或选项卡中 泄漏 DOM 节点

演示网页:示例 9:DOM 泄漏大于预期

若要查看 DOM 泄漏可能大于预期的原因,请在新窗口或选项卡中打开示例网页 示例 9:DOM 泄漏大于预期

分析闭包对内存的影响

若要分析闭包对内存的影响,请尝试此示例:

  1. 在新窗口或选项卡中打开 Eval is evil 演示网页。

  2. 记录堆快照。

  3. 在呈现的网页中,单击“ 带评估的关闭” 按钮。

  4. 记录第二个堆快照。

    在边栏中,第二个快照下面的数字应大于低于第一个快照的数字。 这表示在单击“ 带 eval 的关闭 ”按钮后,网页正在使用更多内存。

  5. 在第二个堆快照中,将视图更改为“比较”,然后将第二个堆快照与第一个堆快照进行比较。

    比较”视图显示已在第二个堆中创建新字符串快照:

    比较视图,显示第二个快照

  6. “比较” 视图中,展开 (字符串) 构造函数。

  7. 单击第一 个 (字符串) 项。

    “保留器”部分已更新,并显示变量largeStr保留“比较”视图中所选的字符串。

    largeStr 条目会自动展开,并显示变量由 eC 函数保留,该函数是定义变量的闭包:

    “保留器”部分,显示 eC 函数保留字符串

提示:用于区分快照中闭包的名称函数

若要轻松区分堆快照中的 JavaScript 闭包,请提供函数名称。

以下示例使用未命名的函数返回 largeStr 变量:

function createLargeClosure() {
    const largeStr = 'x'.repeat(1000000).toLowerCase();

    // This function is unnamed.
    const lC = function() {
        return largeStr;
    };

    return lC;
}

以下示例为 函数命名,以便更轻松地区分堆快照中的闭包:

function createLargeClosure() {
    const largeStr = 'x'.repeat(1000000).toLowerCase();

    // This function is named.
    const lC = function lC() {
        return largeStr;
    };

    return lC;
}

将字符串从堆快照保存并导出到 JSON

内存工具中获取堆快照时,可以将快照中的所有字符串对象导出到 JSON 文件中。 在“内存”工具的“构造函数”部分中,单击条目旁边的(string)全部保存到文件”按钮:

将堆中的所有字符串快照保存到 JSON

内存工具导出一个 JSON 文件,该文件包含堆快照中的所有字符串对象:

JSON 文件中堆快照中的字符串

另请参阅

注意

此页面的部分内容是基于 Google 创建和 共享 的工作进行的修改,并根据 Creative Commons 署名 4.0 国际许可中所述的条款使用。 原始页面 在此处 找到,由 Meggin Kearney (Technical Writer) 创作。

Creative Commons 许可证 此作品根据 Creative Commons 署名 4.0 国际许可获得许可