调查 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 格式的数据。
数据有两个主要部分:
- 元数据,其中包含分析表示内存图的数据数组所需的所有信息。
- 数组数据,其中包含重新创建图形所需的实际数据。
更新此数据格式文档
文件的格式 .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_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_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 项,例如 HTMLDivElement 或 CSSStyleRule。 |
| 串联字符串 | 将两个字符串与 + 运算符连接的结果。 V8 创建一个包含两个源字符串中所有数据的副本的新字符串,而是创建一个对象,其中包含指向两个 ConsString 源字符串的指针。 从 JavaScript 的角度来看,它的行为就像任何其他字符串一样,但从内存分析的角度来看,它是不同的。 |
| 切片字符串 | 子字符串作的结果,例如使用 String.prototype.substr 或 String.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类。