录制堆快照

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

了解如何依次前往内存 > 配置文件 > 堆快照来记录堆快照,以及如何查找内存泄漏问题。

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

拍摄快照

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

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

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

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

可到达对象的总大小。

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

分散的 Item 对象的堆快照。

清除快照

如需移除所有快照,请点击 block 清除所有配置文件

清除所有个人资料。

查看快照

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

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

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

摘要视图

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

包含展开的构造函数的 Summary 视图。

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

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

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

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

摘要中的特殊条目

除了按构造函数分组之外,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 设置 > 实验 > 复选框 显示用于在堆快照中公开内部元素的选项
  2. 打开 Memory 面板,选择 radio_button_checked 堆快照,然后开启 check_box 公开内部参数(包括其他实现专用的详细信息)
  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. 截取第二个堆快照,将其视图更改为 Comparison 视图,并将其与 Snapshot 1 进行比较。

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

与快照 1 对比。

包含关系视图

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

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

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

“Containment”视图。

“保留者”部分

内存面板底部的保留器部分会显示指向视图中所选对象的对象。

“保留者”部分。

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

查找特定对象

若要在回收的堆中查找某个对象,您可以使用 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 子树