Web アプリケーションでのメモリ使用量の調査は困難な場合があります。 DevTools Memory ツールを使用すると、ヒープをスナップショットすることで、Web アプリケーションによってメモリに割り当てられているすべてのオブジェクトを探索できます。 この情報は、最も多くのメモリを消費しているオブジェクトを確認できるため、パフォーマンスの調査に役立ちます。
ただし、 メモリ ツールに 表示されないメモリ データの特定の部分に焦点を当てる必要がある場合があります。 この場合は、DevTools を使用して、メモリ データのセット全体を .heapsnapshot JSON ファイルとしてエクスポートします。
この記事では、独自の視覚化と分析ツールを構築できるように、 .heapsnapshot JSON ファイルの構造と内容について説明します。
ヒープ スナップショットを記録する
.heapsnapshot ファイルをエクスポートするには、まず、次のように、メモリ ツールでヒープ スナップショットを記録する必要があります。
Microsoft Edge で、データをエクスポートする Web サイトに移動します。
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 アプリケーションによって使用されるメモリは、Microsoft Edge によって使用される JavaScript エンジンである V8 によってグラフとして編成されます。 グラフは、 ノード (グラフ上のポイント) と エッジ (ポイント間のリンク) で構成されるデータ型です。
.heapsnapshot ファイル内のデータは、効率的にグラフ化する Web アプリのメモリを表し、ブラウザー プロセスと DevTools 間でデータのグループを転送しやすくなります。
.heapsnapshot ファイルには、数値と文字列の配列を含む JSON オブジェクトとして、ノードとエッジ間の関係のフラット化された表現が含まれています。 ファイルには .heapsnapshot ファイル名拡張子があり、JSON 形式のデータが含まれています。
データには、次の 2 つの主要な部分があります。
- メモリ グラフを表すデータの配列を解析するために必要なすべての情報を含むメタデータ。
- 配列データ。グラフの再作成に必要な実際のデータが含まれます。
このデータ形式のドキュメントの更新
以下に記載されているように、 .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 |
ノードのスクリプトの場所に関する情報が含まれます。 このデータを解析するには、nodes 配列で snapshot.meta.location_fields を使用します。 |
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
.heapsnapshot データの最上位にある nodes 配列には、メモリ グラフのノードを再作成するために必要なすべての情報が含まれています。
この配列を解析するには、次の情報が必要です。
-
snapshot.node_countは、ノードの数を知るために使用されます。 -
snapshot.meta.node_fieldsを使用して、各ノードに含まれるフィールドの数を確認します。
配列内の各ノードは、一連の snapshot.meta.node_fields.length 番号で表されます。 そのため、nodes配列内の要素の合計数は、snapshot.meta.node_fields.lengthで乗算snapshot.node_count。
ノードを再作成するには、サイズsnapshot.meta.node_fields.lengthのグループでnodes配列から数値を読み取る。
次のコード スニペットは、グラフ内の最初の 2 つのノードの 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,
...
]
...
}
| ノード グループ内のインデックス | Name | 説明 |
|---|---|---|
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 では、これらはすべてカテゴリ名 (システム) の下に表示されます。 これらのオブジェクトは内部的であっても、リテーナー パスの重要な部分になる可能性があります。 |
| オブジェクト |
{ x: 2 }やnew Foo(4)など、ユーザー定義オブジェクト。 DevTools に システム/コンテキストとして表示されるコンテキストは、入れ子になった関数によって使用されるため、ヒープに割り当てる必要があった変数を保持します。 |
| ネイティブ | V8 ではなく、Blink レンダリング エンジンによって割り当てられるもの。 これらは主に、 HTMLDivElement や CSSStyleRuleなどの DOM 項目です。 |
| 連結された文字列 | 2 つの文字列を + 演算子と連結した結果。 V8 は、2 つのソース文字列のすべてのデータのコピーを含む新しい文字列を作成するのではなく、2 つのソース文字列へのポインターを含む ConsString オブジェクトを作成します。 JavaScript の観点からは、他の文字列と同様に動作しますが、メモリ プロファイリングの観点からは異なります。 |
| スライスされた文字列 |
String.prototype.substrやString.prototype.substringの使用など、部分文字列操作の結果。 V8 では、元の文字列を指し示し、開始インデックスと長さを指定する SlicedStringを作成することで、文字列データのコピーを回避します。 JavaScript の観点から見ると、スライスされた文字列は他の文字列と同様に機能しますが、メモリ プロファイリングの観点からは異なります。 |
| 配列 | カテゴリ名 (配列) を使用して DevTools に表示されるさまざまな内部リスト。 非表示と同様に、このカテゴリはさまざまなものをグループ化します。 ここでのオブジェクトの多くは、名前 (オブジェクト プロパティ) または (オブジェクト要素) であり、JavaScript オブジェクトの文字列キー付きプロパティまたは数値キー付きプロパティが含まれていることを示します。 |
| コード | スクリプトの量や関数の実行回数に比例して増加するもの。 |
| 人造 | 合成ノードは、メモリ内で実際に割り当てられたものには対応しません。 これらは、さまざまな種類のガベージ コレクション (GC) ルートを区別するために使用されます。 |
エッジ
nodes配列と同様に、edges最上位の配列には、メモリ グラフのエッジを再作成するために必要なすべての要素が含まれています。
ノードと同様に、エッジの合計数は、 snapshot.edge_count に snapshot.meta.edge_fields.lengthを掛けることによって計算できます。 エッジは数値のシーケンスとしても格納されます。これは、サイズ snapshot.meta.edge_fields.lengthのグループで反復処理する必要があります。
ただし、 edges 配列を正しく読み取るために、各ノードが持つエッジの数を認識するため、最初に nodes 配列を読み取る必要があります。
エッジを再作成するには、次の 3 つの情報が必要です。
- エッジの種類。
- エッジ名またはインデックス。
- エッジが接続されているノード。
たとえば、nodes配列の最初のノードを読み取り、そのedge_count プロパティを 4 に設定した場合、edges配列内のsnapshot.meta.edge_fields.length番号の最初の 4 つのグループは、このノードの 4 つのエッジに対応します。
| エッジ グループのインデックス | Name | 説明 |
|---|---|---|
0 |
type |
エッジの種類。 可能 な型については、「Edge 型 」を参照してください。 |
1 |
name_or_index |
数値または文字列を指定できます。 数値の場合は、最上位の strings 配列のインデックスに対応します。ここで、エッジの名前が見つかります。 |
2 |
to_node |
このエッジが接続されている nodes 配列内のインデックス。 |
エッジの種類
edges配列のエッジの数値グループの最初の数値は、その型に対応します。 この番号は、 snapshot.meta.edge_types[0] 配列内の型名を検索するために使用できるインデックスです。
| エッジの種類 | 説明 |
|---|---|
| 内部 | JavaScript で表示される名前に対応していないが、依然として重要なエッジ。 たとえば、関数インスタンスには、関数が定義されたスコープ内にあった変数の状態を表す "コンテキスト" があります。 JavaScript コードが関数の "コンテキスト" を直接読み取る方法はありませんが、リテーナーを調査するときは、これらのエッジが必要です。 |
| 弱い | 弱いエッジは、接続されているノードを維持しないため、保持ビューから省略されます。 弱いエッジのみを指すオブジェクトは、ガベージ コレクション (GC) によって破棄できます。 |
| Hidden | 内部に似ていますが、これらのエッジには一意の名前がなく、代わりに番号が増える順序で番号が付けられます。 |
| ショートカット | 他のパスの読みやすい表現。 この型はまれに使用されます。 たとえば、Function.prototype.bindを使用してバインドされた関数をいくつかのバインドされた引数で作成する場合、V8 は、バインドされた各引数を指すFixedArray (内部型) を指すJSBoundFunctionを作成します。 スナップショットを生成するときに、V8 はバインドされた関数から各バインドされた引数に直接ショートカット エッジを追加し、FixedArrayをバイパスします。 |
| 要素 | キーが数値であるオブジェクト プロパティ。 |
locations
.heapsnapshot データの最上位にある locations 配列には、スナップショット内のノードの一部が作成された場所に関する情報が含まれています。 この配列は、サイズ snapshot.meta.location_fields.lengthのグループによって読み取られる一連の数値で構成されます。 したがって、 snapshot.meta.location_fields に移動して、 locations 配列内の各場所に含まれるフィールドの数と、それらのフィールドが何であるかを知ります。 たとえば、 location_fields に 4 つの項目が含まれている場合、 locations 配列は 4 のグループで読み取られます。
snapshot.meta.location_fields には、各場所の情報が含まれています。
でインデックスを作成する location_fields |
Name | 説明 |
|---|---|---|
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 ファイル形式の詳細については、ファイルを生成するコード (heap-snapshot-generator.hのHeapSnapshotGenerator クラス) を参照してください。