录制堆快照

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

了解如何使用内存记录堆快照 >个人资料 >堆快照并查找内存泄漏。

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

拍摄快照

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

  1. 在要分析性能的网页上,打开 DevTools 并前往内存面板。
  2. 选择 堆快照性能剖析类型,然后选择一个 JavaScript 虚拟机实例,并点击拍摄快照

所选的性能分析类型和 JavaScript 虚拟机实例。

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

可到达对象的总大小。

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

分散的 Item 对象的堆快照。

清除快照

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

清除所有个人资料。

查看快照

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

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

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

摘要视图

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

展开的构造函数的“摘要”视图。

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

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

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

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

构造函数过滤条件

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

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

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

摘要中的特殊条目

除了按构造函数分组之外,摘要视图还会按以下条件对对象进行分组:

  • 内置函数,例如 ArrayObject
  • 按标记分组的 HTML 元素,例如 <div><a><img> 等。
  • 您在代码中定义的函数。
  • 不基于构造函数的特殊类别。

构造函数条目。

(array)

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

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

(compiled code)

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

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

(concatenated string)

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

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

InternalNode

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

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

  1. 打开开发者工具,然后开启 设置 >实验 > 显示堆快照中公开内部构件的选项
  2. 打开内存面板,选择 堆快照,然后开启 公开内部构件(包括额外的实现专用详细信息)
  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 根可以由内置对象映射、符号表、虚拟机线程堆栈、编译缓存、句柄范围和全局句柄组成。
  • 原生对象。“推送”的浏览器对象以便实现自动化,例如 DOM 节点和 CSS 规则。

“包含”视图。

“Retainers”部分

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

“保留器”部分。

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

忽略保留器

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

“忽略此保留器”选项。

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

查找特定对象

如需在收集的堆中查找对象,您可以使用 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 失效时,#tree 下的整个树会成为 GC 的候选对象。

DOM 子树