使用内存工具 (“堆快照”分析类型) 记录堆快照

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

  • 记录 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 中并进行分析后,将显示快照,并且堆快照下的“配置文件”边栏中会显示一个新条目:

可访问对象的总大小

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

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

采取另一个快照

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

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

清除快照

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

删除快照

查看快照

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

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

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

切换视图选择器

从堆快照中省略的项

使用运行本机代码的 getter 实现的属性不会在堆快照中捕获,因为此类属性不存储在 JavaScript 堆上。

不会捕获非字符串值,例如数字。

摘要视图

内存工具中的“摘要”视图列出了:

  • 对象构造函数组。
  • 特殊类别名称,例如 (数组) 、 (编译代码) 或 属性列表,如 {foo, bar, baz}。

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

摘要视图

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

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

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

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

构造函数条目

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

构造函数组

“摘要”视图中的构造函数组可能是内置函数(如 Array 或 Object),也可能是在你自己的代码中定义的函数。

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

特殊类别名称

内存工具中的“摘要”视图包括以下不基于构造函数的特殊类别名称。 其中大多数类别名称都显示在括号中。

类别名称 说明
(数组) 与 JavaScript 中可见的对象不直接对应的各种内部数组对象,例如 JavaScript 数组的内容或 JavaScript 对象的命名属性。
(编译的代码) V8 (Microsoft Edge 的 JavaScript 引擎) 的内部数据需要运行 JavaScript 或 WebAssembly 定义的函数。 V8 自动管理此类别中的内存使用情况:如果某个函数多次运行,V8 会为该函数使用更多内存,以便函数运行得更快。 如果某个函数在一段时间内未运行,V8 可能会删除该函数的内部数据。
(串联字符串) 当两个字符串串联在一起时(例如使用 JavaScript + 运算符时),V8 可能会选择在内部以 串联字符串的形式表示结果。 V8 创建指向这两个字符串的小对象,而不是将两个字符串的所有字符复制到新字符串中。
(对象形状) 有关对象的信息,例如它们具有的属性数以及对它们的原型的引用,V8 在创建和更新对象时会在内部维护这些对象。 这允许 V8 有效地表示具有相同属性的对象。
(切片字符串) 创建子字符串时(例如使用 JavaScript substring 方法时),V8 可能会选择创建 切片字符串 对象,而不是从原始字符串复制所有相关字符。 此新对象包含指向原始字符串的指针,并描述要使用的原始字符串中的字符范围。
(系统) 尚未以任何更有意义的方式分类的各种内部对象。
{foo, bar, baz} 按接口分类的纯 JavaScript 对象 (属性列表) (大括号)。 普通 JavaScript 对象不在名为 Object 的类别中列出,而是由基于对象包含的属性(如 {foo, bar, baz})的名称和类别表示。
InternalNode 在 V8 外部分配的对象(如 Blink 定义的C++对象)Microsoft Edge 的呈现引擎。
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 引擎。

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

按节点类型筛选堆快照

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

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

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

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

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

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

查找特定对象

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

发现 DOM 泄漏

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

对于分离的元素,另请参阅下面的查找 DOM 树内存泄漏 (“堆快照”分析类型>分离) 。

请考虑以下 DOM 树:

DOM 子树

下面的代码示例创建 JavaScript 变量 treeRef 和 leafRef,它们引用树中的两个 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"> 可以垃圾回收元素。 treeRef和 leafRef 必须首先为 null,才能对 元素下的<div id="tree">整个 DOM 树进行垃圾回收。

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

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

演示网页:示例 8:记录堆分配

打开示例网页示例 8:在新窗口或选项卡中 记录堆分配 。

演示网页:示例 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 文件中堆快照中的字符串

查找 DOM 树内存泄漏 (“堆快照”分析类型>分离)

查找和显示网页上所有分离元素的一种方法是使用内存工具的堆快照分析类型,然后在“按类筛选”文本框中键入“分离”,如下所示。 另请参阅修复内存问题中的用于调查分离元素的工具。

以下代码生成分离的 DOM 节点:

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);

此代码创建包含 10 li 个ul子级的节点。 节点由代码引用,但它们在 DOM 树中不存在,因此每个节点都是分离的。

堆快照是标识分离节点的一种方法。 堆快照显示在快照时间点页面的 JS 对象和 DOM 节点之间的内存分布方式。

使用“堆快照”分析类型查找分离的元素

若要使用堆快照分析类型查找分离的元素,

  1. 在新窗口或选项卡中打开网页,例如 “分离元素”演示网页。

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

    DevTools 随即打开。

  3. 在 DevTools 的 “活动栏”中,选择 “内存 (内存工具”图标) 工具。

    如果该选项卡不可见,请单击“ 更多工具 (更多工具”图标) 按钮,然后选择“ 内存”。 此时会打开 “内存 ”工具:

    打开内存工具

    如果未显示“堆快照”选项按钮,因为已显示配置文件,请单击左上角的“配置文件” (“配置文件”图标) 。

    此时无需选择“堆快照”选项按钮,因为网页尚未生成任何分离的元素。

    生成将由 Room 类的 JavaScript 实例存储的消息:

  4. 在演示网页中,单击“ 快速流量 ”按钮。

    演示网页开始生成消息并将其显示在网页中:

    在演示网页中生成一些消息

  5. 显示某些消息后,单击演示网页中的 “停止 ”按钮。

    每条消息都是 <div class="message"> 类的 Room 1 实例引用的 Room 元素。 网页 DOM 树中没有分离的元素,因为所有消息元素都附加到 Room 类的当前 Room 1 实例。

    更改为 Room 类的不同实例,使元素分离:

  6. 在演示网页中,单击“ 会议室 2 ”按钮,该按钮对应于类的另一个实例 Room 。

    在网页中,消息消失:

    会议室 2 初始视图

    为 Room 类的 Room 1 实例生成的消息 (<div class="message"> 元素) 不再附加到 DOM,但仍由 Room 类的 Room 1 实例引用。 它们是分离的元素,这可能会导致内存泄漏,除非网页将再次使用这些元素。

    获取分离元素的列表:

  7. 在 DevTools 的 内存 工具中,单击“ 回收垃圾 ” (“回收垃圾”图标) 图标:

    获取堆快照

    浏览器运行垃圾回收,删除 JavaScript 对象不再引用的任何节点。

  8. 在“内存”工具中,选择“堆快照”选项按钮。

  9. 单击内存工具底部的“采取快照”按钮。

    处理、加载快照,然后在“堆快照”部分的“配置文件”边栏中列出。

  10. 在 “按类筛选 ”文本框中,键入 “分离”:

    筛选分离的节点,并展开分离的节点

    将显示无法进行垃圾回收的分离 DOM 元素。

    标识引用特定分离元素的 JavaScript 代码:

  11. 在堆快照中,展开“分离”对象(如“分离的 <div>”),然后选择“分离<的 div class=”message“>节点。

    信息显示在内存工具底部的“保留器”窗格中。

  12. 在“保留器”窗格中,单击“room.js:13数组”下“会议室中未装载”项的链接。 “源”工具随即打开,显示 room.js,滚动到第 13 行:

    源工具中的 JavaScript 源代码

  13. 若要检查可能的内存泄漏,请研究使用 unmounted 数组的代码,并确保在不再需要节点时删除对节点的引用。

  14. 若要返回到 内存 工具,请在 地址栏中选择 “内存” 工具。

有关查看分离元素的其他方法,请参阅修复内存问题中的用于调查分离元素的工具。

另请参阅

外部:

注意

此页面的某些部分是根据 Google 创建和共享的作品所做的修改,并根据 Creative Commons Attribution 4.0 International License 中描述的条款使用。 原始页面 在此处 找到,由 Meggin Kearney 创作。

Creative Commons 许可证 本作品根据 Creative Commons Attribution 4.0 International License 获得许可。