录制堆快照

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

了解如何通过内存 > 配置文件 > 堆快照记录堆快照以及查找内存泄漏问题。

堆分析器按网页的 JavaScript 对象和相关 DOM 节点显示内存分布情况。用它来截取 JS 堆快照、分析内存图表、比较快照以及查找内存泄漏。如需了解详情,请参阅对象保留树

拍摄快照

如需截取堆快照,请执行以下操作:

  1. 在要分析的页面上,打开开发者工具并导航到 Memory 面板。
  2. 选择 radio_button_checked 堆快照性能剖析类型,然后选择一个 JavaScript 虚拟机实例,并点击拍摄快照

选定的分析类型和 JavaScript 虚拟机实例。

内存面板加载并解析快照时,会在 HEAP 快照部分中的快照标题下方显示可访问的 JavaScript 对象的总大小。

可访问对象的总大小。

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

分散的 Item 对象的堆快照。

清除快照

要移除所有快照,请点击屏蔽 清除所有分析

清除所有个人资料。

查看快照

若要出于不同目的从不同角度检查快照,请从顶部的下拉菜单中选择一个视图:

查看 内容 Purpose
总结 按构造函数名称分组的对象。 使用它根据类型搜索对象及其内存使用情况。有助于跟踪 DOM 泄漏
比较 两个快照之间的差异。 使用它来比较操作前后的两个(或更多)快照。检查已释放内存和引用计数中的增量,确认是否存在内存泄漏及其原因。
遏制 堆内容 提供对象结构的更好视图,并帮助分析全局命名空间(窗口)中引用的对象,以找到其周围环境。您可以使用它来分析闭包并在较低级别深入了解您的对象。
统计信息 内存分配饼图 查看分配给代码、字符串、JS 数组、类型化数组和系统对象的内存部分的实际大小。

从顶部的下拉菜单中选择“摘要”视图。

摘要视图

最初,堆快照会在摘要视图中打开,并且会在列中列出构造函数。您可以展开构造函数以查看它们实例化的对象。

具有展开构造函数的 Summary 视图。

如需过滤掉不相关的构造函数,请在 Summary 视图顶部的 Class filter 中输入要检查的名称。

构造函数名称旁的数字表示使用该构造函数创建的对象总数。摘要视图还会显示以下列:

  • Distance 显示使用节点最短简单路径时到根节点的距离。
  • 浅层大小显示由特定构造函数创建的所有对象的浅层大小的总和。浅层大小是指对象本身占用的内存大小。通常,数组和字符串具有较大的浅层大小。另请参阅对象大小
  • 保留的大小显示同一组对象中保留的大小上限。保留大小是指您可以通过删除对象并使其依赖项不再可访问来释放的内存大小。另请参阅对象大小

展开构造函数时,Summary 视图会显示该构造函数的所有实例。每个实例都会在相应列中获得其浅层大小和保留大小的详细信息。@ 字符后面的数字是对象的唯一 ID。让您可以按对象比较堆快照。

构造函数过滤条件

借助摘要视图,您可以根据低效内存用量的常见情况过滤构造函数。

要使用这些过滤条件,请从操作栏最右侧的下拉菜单中选择下列选项之一:

  • 所有对象:当前快照捕获的所有对象。默认设置。
  • 在快照 1 之前分配的对象:在截取第一个快照之前创建并保留在内存中的对象。
  • 在快照 1 和快照 2 之间分配的对象:查看最新快照与上一快照之间的对象差异。每个新快照都会向下拉列表中添加此过滤条件的增量。
  • 重复的字符串:在内存中多次存储的字符串值。
  • 由已分离的节点保留的对象:由于已分离的 DOM 节点引用而保持活跃状态的对象。
  • 由开发者工具控制台保留的对象:由于通过开发者工具控制台进行求值或与之交互而保留在内存中的对象。

摘要中的特殊条目

除了按构造函数分组之外,Summary 视图还按照以下标准对对象进行分组:

  • 内置函数,例如 ArrayObject
  • 您在代码中定义的函数。
  • 不基于构造函数的特殊类别。

构造函数条目。

(array)

此类别包括各种类似于数组的内部对象,这些对象与 JavaScript 中显示的对象不直接对应。

例如,JavaScript Array 对象的内容存储在名为 (object elements)[] 的次要内部对象中,以便更轻松地调整大小。同样,JavaScript 对象中的已命名属性通常存储在名为 (object properties)[] 的次要内部对象中,这些对象也列在 (array) 类别中。

(compiled code)

此类别包含 V8 运行由 JavaScript 或 WebAssembly 定义的函数所需的内部数据。每个函数都可以用各种方式表示,从小和慢,到大和快。

V8 会自动管理此类别的内存用量。如果某个函数运行多次,V8 会为该函数使用更多内存,以提高其运行速度。如果某个函数有一段时间没有运行,V8 可能会清除该函数的内部数据。

(concatenated string)

V8 将两个字符串串联时(例如使用 JavaScript + 运算符),它可能会选择在内部将结果表示为“串联的字符串”,也称为 Rope 数据结构。

V8 会分配一个包含名为 firstsecond 的内部字段(指向两个源字符串)的小对象,而不是将两个源字符串的所有字符都复制到一个新的字符串中。这可让 V8 节省时间和内存。从 JavaScript 代码的角度来看,这些只是普通的字符串,其行为与其他任何字符串一样。

InternalNode

此类别表示在 V8 之外分配的对象,例如 Blink 定义的 C++ 对象。

要查看 C++ 类名称,请使用 Chrome for Testing 并执行以下操作:

  1. 打开开发者工具,然后依次选择 settings 设置 > 实验 > check_box 显示用于在堆快照中公开内部项的选项
  2. 打开内存面板,选择 radio_button_checked 堆快照,然后开启 radio_button_checked 公开内部构件(包括额外的实现专用详细信息)
  3. 重现导致 InternalNode 保留大量内存的问题。
  4. 拍摄堆快照。在此快照中,对象具有 C++ 类名称,而不是 InternalNode
(object shape)

V8 中的快速属性中所述,V8 会跟踪隐藏类(或形状),以便高效地表示属性相同、顺序相同的多个对象。此类别包含这些隐藏的类,称为 system / Map(与 JavaScript Map 无关)以及相关数据。

(sliced string)

V8 需要获取子字符串时(例如在 JavaScript 代码调用 String.prototype.substring() 时),V8 可能会选择分配一个切片字符串对象,而不是从原始字符串中复制所有相关字符。这个新对象包含一个指向原始字符串的指针,用于说明要使用的原始字符串中的哪些字符范围。

从 JavaScript 代码的角度来看,这些只是普通的字符串,其行为与其他任何字符串一样。如果切片字符串保留了大量内存,则程序可能触发了问题 2869,并可能受益于刻意采取措施将切片字符串“扁平化”。

system / Context

system / Context 类型的内部对象包含闭包(嵌套函数可以访问的 JavaScript 范围)中的局部变量。

每个函数实例都包含一个指向其执行的 Context 的内部指针,以便它可以访问这些变量。即使 Context 对象无法从 JavaScript 中直接可见,您也可以直接控制它们。

(system)

此类别包含尚未(尚未)以更有意义的方式分类的各种内部对象。

比较视图

比较视图中,您可以通过相互比较多个快照来查找泄露的对象。例如,执行一项操作并撤销操作(如打开和关闭文档)时,不应留下额外的对象。

如需验证特定操作不会造成泄露,请执行以下操作:

  1. 在执行操作之前截取堆快照。
  2. 执行一项操作。也就是说,以您认为可能导致信息泄露的方式与网页互动。
  3. 执行反向操作。也就是说,做相反的互动,重复几次。
  4. 拍摄第二个堆快照并将其视图更改为比较,将其与快照 1 进行比较。

Comparison(比较)视图会显示两个快照之间的差异。展开总计条目时,系统会显示已添加和删除的对象实例:

与快照 1 相比。

“包含”视图

Containment 视图是对应用对象结构的“鸟瞰”。它可让您深入了解函数闭包、观察共同构成 JavaScript 对象的虚拟机内部对象,并在非常低的级别了解应用使用的内存量。

该视图提供了多个入口点:

  • DOMWindow 对象的对象。JavaScript 代码的全局对象。
  • GC 根。虚拟机的垃圾回收器使用的 GC 根。GC 根可以由内置对象映射、符号表、虚拟机线程堆栈、编译缓存、句柄范围和全局句柄组成。
  • 原生对象。被“推送”到 JavaScript 虚拟机内以允许自动化的浏览器对象,例如 DOM 节点和 CSS 规则。

“包含”视图。

“Retainers”部分

位于 Memory 面板底部的 Retainers 部分会显示指向视图中所选对象的对象。当您在任何视图中(统计信息除外)中选择其他对象时,内存面板会更新保留器部分。

“Retainers”部分。

在此示例中,所选字符串由 Item 实例的 x 属性保留。

忽略保留器

您可以隐藏保留器,以查找保留所选对象的任何其他对象。使用此选项时,您不必先从代码中移除此保留器,然后再重新截取堆快照。

下拉菜单中的“忽略此保留器”选项。

要隐藏保留器,请右键点击并选择忽略此保留器。忽略的保留器在 Distance 列中标记为 ignored。如需停止忽略所有保留器,请点击顶部操作栏中的 playlist_remove 恢复忽略的保留器

查找特定对象

如需在收集的堆中查找对象,您可以使用 Ctrl + F 进行搜索,然后输入对象 ID。

为函数命名以区分闭包

为函数命名有助于区分快照中的闭包。

例如,以下代码未使用命名函数:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

而以下示例则是:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

闭包中的命名函数。

发现 DOM 泄漏

堆分析器能够反映浏览器原生对象(DOM 节点和 CSS 规则)与 JavaScript 对象之间的双向依赖关系。这有助于发现由于被遗忘的已分离 DOM 子树漂浮而产生的不可见泄漏。

DOM 泄漏可能比您想象的更大。请参考以下示例。何时收集 #tree 垃圾回收?

  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 #tree can be garbage collected

#leaf 会维护对其父级 (parentNode) 的引用,并以递归方式直至 #tree,因此,仅当 leafRef 为 null 时,#tree 下的整个树才会成为 GC 的候选对象。

DOM 子树