调查 Web 应用程序中的内存使用情况可能很困难。 使用 DevTools 内存 工具,可以通过获取堆快照浏览 Web 应用程序在内存中分配的所有对象。 此信息对于性能调查非常有用,因为可以找出哪些对象消耗了最多的内存。

但是,有时可能需要专注于内存工具未显示的 内存 数据的特定部分。 在这种情况下,请使用 DevTools 将整个内存数据集导出为 .heapsnapshot JSON 文件。

本文介绍 JSON 文件的结构和内容, .heapsnapshot 以便你可以生成自己的可视化效果和分析工具。

记录堆快照

若要导出 .heapsnapshot 文件,首先需要在 内存 工具中记录堆快照,如下所示:

  • 在 Microsoft Edge 中,导航到要从中导出数据的网站。

  • Ctrl+Shift+I (Windows、Linux) 或 Command+Option+I (macOS) 打开 Devtools。

  • 打开 “内存” 工具。

  • 选择“ 堆快照 然后单击” 采取快照 ”。

    有关详细信息,请参阅 使用内存工具记录堆快照 (“堆快照”分析类型)

    导出和查看 .heapsnapshot 文件

    录制堆快照后,可以将其导出。

  • “内存 工具”左侧边栏中,单击刚刚记录的堆快照项旁边的“ 保存 ”。

  • 将文件扩展名从 .heapsnapshot .json 更改为 ,以便更轻松地在文本编辑器中打开文件。

  • 在文本编辑器(如Visual Studio Code)中打开保存的文件。

  • 若要使 JSON 更易于阅读,请在 Visual Studio Code 中右键单击代码中的任意位置,然后选择“ 设置文档格式 ”。

    通常,每次记录和导出堆快照时,生成的 .heapsnapshot 文件都是不同的。 堆快照是基于 DevTools 中当前正在检查的 Web 应用程序的内容动态生成的。

    .heapsnapshot 文件格式概述

    Web 应用程序使用的内存按 V8 组织为图形,V8 是 Microsoft Edge 使用的 JavaScript 引擎。 图形是由图形上的 节点 (点组成的数据类型,) 和 边缘 (点) 之间的链接。

    文件中的数据 .heapsnapshot 表示 Web 应用的内存,该内存可有效地绘制图形,并使在浏览器进程和 DevTools 之间传输数据组变得更加容易。 该文件 .heapsnapshot 包含节点和边缘之间关系的平展表示形式,作为包含数字和字符串数组的 JSON 对象。 该文件具有 .heapsnapshot 文件扩展名,并且包含 JSON 格式的数据。

    数据有两个main部分:

  • 元数据,其中包含分析表示内存图的数据数组所需的所有信息。
  • 数组数据,其中包含重新创建图形所需的实际数据。
  • 更新此数据格式文档

    文件的格式 .heapsnapshot (如下所述)可能会随着 V8 和 DevTools 的发展而更改。 如果发现文档中存在差异,请在 MicrosoftDocs/edge-developer 存储库 中提供反馈。

    .heapsnapshot 数据的架构 .heapsnapshot JSON 数据包含具有以下属性的根对象:

    "snapshot": {}, "nodes": [], "edges": [], "trace_function_infos": [], "trace_tree": [], "samples": [], "locations": [], "strings": [] nodes 重新创建图形节点所需的所有信息。 若要分析此数据,请使用 snapshot.meta.node_types snapshot.meta.node_fields Array edges 重新创建图形边缘所需的所有信息。 若要分析此数据,请使用 snapshot.meta.edge_types snapshot.meta.edge_fields Array trace_function_infos Array trace_tree Array samples Array locations 包含有关节点的脚本位置的信息。 若要分析此数据,请与 数组一起使用 snapshot.meta.location_fields nodes Array strings 内存中保留的所有字符串的数组。 这些字符串可以是任何字符串,例如用户定义的字符串或代码。 Array "snapshot": { "meta": {}, "node_count": 123, "edge_count": 456, "trace_function_count": 0 snapshot.meta.node_fields ,了解每个节点具有多少个字段。

    数组中的每个节点都由一系列 snapshot.meta.node_fields.length 数字表示。 因此,数组 snapshot.node_count 中的 nodes 元素总数乘以 snapshot.meta.node_fields.length

    若要重新创建节点,请按大小 snapshot.meta.node_fields.length 组从 nodes 数组中读取数字。

    以下代码片段显示了 node_fields 图中前两个节点的元数据和数据:

    "snapshot": { "meta": { "node_fields": [ "type", "name", "id", "self_size", "edge_count", "trace_node_id", "detachedness" "nodes": [ 9,1,1,0,10,0,0, 2,1,79,12,1,0,0, Hidden 与用户可控制的 JavaScript 对象不直接对应的 V8 内部元素。 在 DevTools 中,所有这些内容都显示在 系统) ( 类别名称下。 即使这些对象是内部对象,它们也可以是保留器路径的重要组成部分。 Object 任何用户定义的对象,例如 { x: 2 } new Foo(4) 。 上下文(在 DevTools 中显示为 系统/上下文) 包含必须在堆上分配的变量,因为它们由嵌套函数使用。 由 Blink 呈现引擎而不是 V8 分配的项。 这些项主要是 DOM 项,例如 HTMLDivElement CSSStyleRule 。 串联字符串 将两个字符串与 + 运算符连接的结果。 V8 创建一个包含两个源字符串中所有数据的副本的新字符串,而是创建一个对象,其中包含指向两个 ConsString 源字符串的指针。 从 JavaScript 的角度来看,它的行为就像任何其他字符串一样,但从内存分析的角度来看,它是不同的。 切片字符串 子字符串作的结果,例如使用 String.prototype.substr String.prototype.substring 。 V8 通过改为创建 , SlicedString 来避免复制字符串数据,该数据指向原始字符串并指定起始索引和长度。 从 JavaScript 的角度来看,切片字符串的作用与任何其他字符串类似,但从内存分析的角度来看,它是不同的。 Array 各种内部列表显示在 DevTools 中,其类别名称 (数组) 。 与“隐藏”一样,此类别将各种内容组合在一起。 此处的许多对象 (对象属性命名,) (对象元素) ,指示它们包含 JavaScript 对象的字符串键属性或数字键属性。 与脚本数量成正比增长的内容,以及/或函数运行的次数。 合成节点不对应于内存中实际分配的任何内容。 它们用于区分不同类型的垃圾回收 (GC) 根。

    nodes 数组类似, edges 顶级数组包含重新创建内存图边缘所需的所有元素。

    与节点类似,可以通过乘以 snapshot.edge_count snapshot.meta.edge_fields.length 来计算边缘的总数。 边缘还存储为数字序列,需要按大小 snapshot.meta.edge_fields.length 组循环访问这些序列。

    但是,若要正确读取 edges 数组,首先需要读取 nodes 数组,因为每个节点知道它有多少边缘。

    若要重新创建边缘,需要三条信息:

  • 边缘类型。
  • 边缘名称或索引。
  • 边缘连接到的节点。
  • 例如,如果读取数组中的 nodes 第一个节点,并且其 edge_count 属性设置为 4,则数组中的 edges 前四组 snapshot.meta.edge_fields.length 数字对应于此节点的四个边缘。

    边缘组中的索引 与 JavaScript 可见名称不对应,但仍很重要的边缘。 例如,函数实例具有一个“上下文”,表示定义函数的范围中的变量的状态。 JavaScript 代码无法直接读取函数的“上下文”,但在调查保留器时需要这些边缘。 弱边缘不会使其连接到的节点保持活动状态,因此从“保留器”视图中省略。 垃圾回收 (GC) 可以丢弃任何仅指向弱边缘的对象。 Hidden 与 Internal 类似,只是这些边缘没有唯一的名称,而是按递增顺序编号。 其他一些路径的更易于阅读的表示形式。 很少使用此类型。 例如,如果使用 Function.prototype.bind 创建具有某些绑定参数的绑定函数,V8 会创建一个 JSBoundFunction FixedArray ,它指向 (内部类型) ,该类型指向每个绑定参数。 生成快照时,V8 将绑定函数中的快捷方式边缘直接添加到每个绑定参数,绕过 FixedArray 。 键为数字的对象属性。

    位于 locations 数据顶级的 .heapsnapshot 数组包含有关创建快照中的某些节点的位置的信息。 此数组由一系列数字组成,这些数字按大小 snapshot.meta.location_fields.length 组读取。 因此,我们将转到 snapshot.meta.location_fields 了解数组中 locations 每个位置包含的字段数,以及这些字段是什么。 例如,如果 location_fields 包含 4 个项,则应 locations 按组 4 读取数组。

    snapshot.meta.location_fields 包含每个位置的信息:

    索引 location_fields

    数组中的 locations 第一个位置是 7,9,0,0, 。 此位置与从数组中的 nodes 索引 7 开始的节点信息组相关联。 因此,节点包含以下键/值对:

    "type": 2,
    "name": 1,
    "id": 79,
    "self_size": 12,
    "edge_count": 1,
    "trace_node_id": 0,
    "detachedness": 0,
    "script_id": 9,
    "line" 0,
    "column": 0,
    

    若要了解有关文件格式的详细信息 .heapsnapshot ,请参阅生成文件的代码,即 HeapSnapshotGenerator 中的 heap-snapshot-generator.h类。

    HeapSnapshot 类 (行 142) heapSnapshotGenerator 类 (行 522)