堆快照文件格式

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

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

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

记录堆快照

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

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

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

  3. 打开 “内存” 工具。

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

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

导出和查看 .heapsnapshot 文件

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

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

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

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

  4. 若要使 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": []
}
属性 说明 格式
snapshot 包含有关内存图数据格式及其大小的所有信息。 Object
nodes 重新创建图形节点所需的所有信息。 若要分析此数据,请使用 snapshot.meta.node_typessnapshot.meta.node_fields Array
edges 重新创建图形边缘所需的所有信息。 若要分析此数据,请使用 snapshot.meta.edge_typessnapshot.meta.edge_fields Array
trace_function_infos 尚未记录 Array
trace_tree 尚未记录 Array
samples 尚未记录 Array
locations 包含有关节点的脚本位置的信息。 若要分析此数据,请与 数组一起使用snapshot.meta.location_fieldsnodes Array
strings 内存中保留的所有字符串的数组。 这些字符串可以是任何字符串,例如用户定义的字符串或代码。 Array

快照

{
    "snapshot": {     
        "meta": {},
        "node_count": 123,
        "edge_count": 456,
        "trace_function_count": 0
    }
    ...
}
属性 说明 格式
meta 包含有关内存图数据中包含的每个对象的形状和大小的信息的属性。 Object
node_count 内存图中的节点总数。 Number
edge_count 内存图中的边缘总数。 Number
trace_function_count 内存图中跟踪函数的总数。 Number

快照元数据

{
    "snapshot": {
        "meta": {
            "node_fields": [],
            "node_types": [],
            "edge_fields": [],
            "edge_types": []
        }
    }
    ...
}
属性 说明 格式
node_fields 重新创建节点所需的所有属性的列表。 Array
node_types 重新创建节点所需的所有属性的类型。 类型数与 中 node_fields定义的属性数相同。 Array
edge_fields 重新创建边缘所需的所有属性的列表。 Array
edge_types 重新创建边缘所需的所有属性的类型。 类型数与 中的 edge_fields属性数相同。 Array

下面是元数据对象的一个示例:

{
    "snapshot": {
        "meta": {
            "node_fields": [
                "type",
                "name",
                "id",
                "self_size",
                "edge_count",
                "trace_node_id",
                "detachedness"
            ],
            "node_types": [
                [
                    "hidden",
                    "array",
                    "string",
                    "object",
                    "code",
                    "closure",
                    "regexp",
                    "number",
                    "native",
                    "synthetic",
                    "concatenated string",
                    "sliced string",
                    "symbol",
                    "bigint",
                    "object shape"
                ],
                "string",
                "number",
                "number",
                "number",
                "number",
                "number"
            ],
            "edge_fields": [
                "type",
                "name_or_index",
                "to_node"
            ],
            "edge_types": [
                [
                    "context",
                    "element",
                    "property",
                    "internal",
                    "hidden",
                    "shortcut",
                    "weak"
                ],
                "string_or_number",
                "node"
            ]
        }
    }
}

Nodes

位于 nodes 数据顶层的 .heapsnapshot 数组包含重新创建内存图节点所需的所有信息。

若要分析此数组,需要以下信息:

  • snapshot.node_count,了解有多少个节点。
  • 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,
        ...
    ]
    ...
}
节点组中的索引 名称 说明
0 type 节点的类型。 请参阅下面的 节点类型
1 name 节点的名称。 这是一个数字,它是顶级 strings 数组中的索引。 若要查找实际名称,请使用索引号查找顶级 strings 数组中的字符串。
2 id 节点的唯一 ID。
3 self_size 节点的大小(以字节为单位)。
4 edge_count 连接到此节点的边缘数。
5 trace_node_id 跟踪节点的 ID
6 detachedness 是否可以从 window 全局对象访问此节点。 0 表示节点未分离;可以从全局对象访问 window 节点。 1 表示节点分离;无法从 window 全局对象访问 节点。

节点类型

数组中节点 nodes 的数字组中的第一个数字对应于其类型。 此数字是一个索引,可用于查找数组中的 snapshot.meta.node_types[0] 类型名称。

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

边缘

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

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

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

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

  • 边缘类型。
  • 边缘名称或索引。
  • 边缘连接到的节点。

例如,如果读取数组中的nodes第一个节点,并且其edge_count属性设置为 4,则数组中的edges前四组snapshot.meta.edge_fields.length数字对应于此节点的四个边缘。

边缘组中的索引 名称 说明
0 type 边缘的类型。 请参阅 边缘类型 ,了解可能的类型。
1 name_or_index 这可以是数字或字符串。 如果是数字,则对应于顶级 strings 数组中的索引,可在其中找到边缘的名称。
2 to_node 此边缘连接到的 nodes 数组中的索引。

边缘类型

数组中边缘 edges 的数字组中的第一个数字对应于其类型。 此数字是一个索引,可用于查找数组中的 snapshot.meta.edge_types[0] 类型名称。

边缘类型 说明
内部 与 JavaScript 可见名称不对应,但仍很重要的边缘。 例如,函数实例具有一个“上下文”,表示定义函数的范围中的变量的状态。 JavaScript 代码无法直接读取函数的“上下文”,但在调查保留器时需要这些边缘。
弱边缘不会使其连接到的节点保持活动状态,因此从“保留器”视图中省略。 垃圾回收 (GC) 可以丢弃任何仅指向弱边缘的对象。
Hidden 与 Internal 类似,只是这些边缘没有唯一的名称,而是按递增顺序编号。
快捷方式 其他一些路径的更易于阅读的表示形式。 很少使用此类型。 例如,如果使用 Function.prototype.bind 创建具有某些绑定参数的绑定函数,V8 会创建一个 JSBoundFunctionFixedArray ,它指向 (内部类型) ,该类型指向每个绑定参数。 生成快照时,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 名称 说明
0 object_index 数组中 snapshot.nodes 与此位置关联的节点的索引。
1 script_id 创建关联节点的脚本的 ID。
2 line 在创建节点的脚本中创建节点的行号。
3 column 创建节点的脚本中创建节点的列号。

下面的代码示例演示如何将 snapshot.locations 数组链接到数组 snapshot.nodes

{
    "snapshot": {
        "meta": {
            "location_fields": [
                "object_index",
                "script_id",
                "line",
                "column"
            ]
            ...
        }
        ...
    },
    "nodes": [
        9,1,1,0,10,0,0,
        2,1,79,12,1,0,0,
        ...
    ],
    "locations":[
        7,9,0,0,
        113792,3,25,21,
        ...
    ],
    ...
}

数组中的 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类。