メモリ ツールを使用してヒープ スナップショットを記録する
メモリ ツールのヒープ プロファイラーを使用して、次の操作を行います。
- JavaScript ヒープ (JS ヒープ) スナップショットを記録します。
- メモリ グラフを分析します。
- スナップショットを比較します。
- メモリ リークを見つけます。
DevTools ヒープ プロファイラーは、JavaScript オブジェクトと、レンダリングされた Web ページ上の関連 DOM ノードによって使用されるメモリ分布を示します。
スナップショットを取得する
分析する Web ページを開きます。 たとえば、新しいウィンドウまたはタブで [散布図オブジェクト ] デモ ページを開きます。
DevTools を開くには、Web ページを右クリックし、[ 検査] を選択します。 または、 Ctrl + Shift + I (Windows、Linux) または Command + Option + I (macOS) を押します。 DevTools が開きます。
DevTools の アクティビティ バーで、[ メモリ ] タブを選択します。そのタブが表示されない場合は、[ その他のツール ] () ボタンをクリックします。
[プロファイルの種類の選択] セクションで、[ヒープ スナップショット] オプション ボタンを選択します。
[ JavaScript VM インスタンスの選択] で、プロファイリングする JavaScript VM を選択します。
[スナップショットの取得] ボタンをクリックします。
新しく記録されたヒープ スナップショットが DevTools に読み込まれ、解析されると、スナップショットが表示され、[HEAP SNAPSHOTS] の [プロファイル] サイドバーに新しいエントリが表示されます。
新しいサイドバー項目の下の番号は、到達可能な JavaScript オブジェクトの合計サイズを示しています。 ヒープ スナップショットのオブジェクト サイズの詳細については、「メモリの用語」の「オブジェクトのサイズと距離」を参照してください。
スナップショットは、グローバル オブジェクトから到達可能なメモリ グラフからのオブジェクトのみを表示します。 スナップショットの取得は、常にガベージ コレクションから始まります。
別のスナップショットを取得する
メモリ ツールに既に表示されているときに別のスナップショットを取得するには、サイドバーで、既存のスナップショットの上にある [プロファイル] をクリックします。
スナップショットをクリアする
[メモリ] ツールからすべてのスナップショットをクリアするには、[すべてのプロファイルをクリアする] () アイコンをクリックします。
スナップショットの表示
ヒープ スナップショットは、 メモリ ツールで複数の異なる方法で表示できます。 UI でヒープ スナップショットを表示する各方法は、異なるタスクに対応します。
View | コンテンツ | 使用対象 |
---|---|---|
概要 | コンストラクター名でグループ化されたオブジェクトを表示します。 | コンストラクター名でグループ化された型に基づいて、オブジェクトと使用するメモリを検索します。 DOM リークを追跡するのに役立ちます。 |
比較 | 2 つのスナップショットの違いを表示します。 | 操作の前後から 2 つ (またはそれ以上) のメモリ スナップショットを比較する。 解放されたメモリ内のデルタを検査し、参照カウントを検査すると、メモリ リークの存在と原因を確認し、その原因を特定するのに役立ちます。 |
コンテインメント | ヒープの内容の探索を許可します。 | オブジェクト構造をより適切に表示し、グローバル名前空間 (ウィンドウ) で参照されているオブジェクトを分析して、オブジェクトを保持している内容を調べるのに役立ちます。 クロージャを分析し、低レベルでオブジェクトを掘り下げるために使用します。 |
ビューを切り替えるには、 メモリ ツールの上部にあるドロップダウン リストを使用します。
注:
すべてのプロパティが JavaScript ヒープに格納されているわけではありません。 ネイティブ コードを実行するゲッターを使用して実装されたプロパティはキャプチャされません。 また、数値などの文字列以外の値はキャプチャされません。
概要ビュー
最初に、ヒープ スナップショットが [概要] ビューで開き、コンストラクターの一覧が表示されます。
リスト内の各コンストラクターを展開して、そのコンストラクターを使用してインスタンス化されたオブジェクトを表示できます。
一覧の各コンストラクターについて、 Summary ビューには、コンストラクターで作成されたオブジェクトの合計数を示す 、×123 などの数値も表示されます。 [概要] ビューには、次の列も表示されます。
列名 | 説明 |
---|---|
Distance | ノードの最短の単純パスを使用して、ルートまでの距離を表示します。 「メモリ内 の距離 」 の用語を参照してください。 |
浅いサイズ | 特定のコンストラクター関数によって作成されたすべてのオブジェクトの浅いサイズの合計を表示します。 浅いサイズは、オブジェクトによって直接保持される JavaScript ヒープのサイズです。 JavaScript オブジェクトは、オブジェクトの直接保持されたメモリに値ではなく、オブジェクトの説明のみを格納するため、通常、オブジェクトの浅いサイズは小さくなります。 ほとんどの JavaScript オブジェクトは、JavaScript ヒープ内の他の場所にある バッキング ストア に値を格納し、オブジェクトが直接所有する JavaScript ヒープの部分にのみ小さなラッパー オブジェクトを公開します。 「メモリの 浅いサイズ 」 の用語を参照してください。 |
保持サイズ | 同じオブジェクト のセット間で保持される最大サイズを表示します。 オブジェクトが削除された後に解放できるメモリのサイズ (および依存オブジェクトに到達できなくなります) は、保持サイズと呼ばれます。 「メモリの用語」の「保持サイズ」を参照してください。 |
サマリー ビューでコンストラクターを展開すると、コンストラクターのすべてのインスタンスが表示されます。 インスタンスごとに、浅いサイズと保持されたサイズが対応する列に表示されます。 文字の後の @
数値はオブジェクトの一意の ID であり、オブジェクトごとにヒープ スナップショットを比較できます。
サマリー ビューのコンストラクター エントリ
メモリ ツールの [概要] ビューには、オブジェクト コンストラクター グループが一覧表示されます。
Summary ビューのコンストラクター グループは、などのArray
Object
組み込み関数であるか、独自のコードで定義されている関数である可能性があります。
特定のコンストラクターによってインスタンス化されたオブジェクトの一覧を表示するには、コンストラクター グループを展開します。
サマリー ビューの特殊なカテゴリ名
サマリー ビューには、コンストラクターに基づいていない特殊なカテゴリ名も含まれています。 これらの特別なカテゴリは次のとおりです。
カテゴリ名 | 説明 |
---|---|
(配列) | JavaScript 配列の内容や JavaScript オブジェクトの名前付きプロパティなど、JavaScript から表示されるオブジェクトに直接対応しない、さまざまな内部配列のようなオブジェクト。 |
(コンパイル済みコード) | V8 (Microsoft Edge の JavaScript エンジン) が JavaScript または WebAssembly によって定義された関数を実行するために必要な内部データ。 V8 は、このカテゴリのメモリ使用量を自動的に管理します。関数が何度も実行される場合、V8 はその関数に対してより多くのメモリを使用して、関数の実行速度が速くなります。 関数がしばらく実行されていない場合、V8 はその関数の内部データを削除する可能性があります。 |
(連結された文字列) | JavaScript + 演算子を使用する場合など、2 つの文字列が連結されている場合、V8 は連結 された文字列として内部的に結果を表す場合があります。 V8 は、2 つの文字列のすべての文字を新しい文字列にコピーするのではなく、2 つの文字列を指す小さなオブジェクトを作成します。 |
InternalNode | V8 の外部に割り当てられたオブジェクト (Microsoft Edge のレンダリング エンジン Blink によって定義された C++ オブジェクトなど)。 |
(オブジェクト図形) | オブジェクトに関する情報(持つプロパティの数、プロトタイプへの参照など)、オブジェクトの作成時と更新時に V8 が内部的に保持します。 これにより、V8 は同じプロパティを持つオブジェクトを効率的に表すことができます。 |
(スライスされた文字列) | JavaScript substring メソッドを使用する場合など、部分文字列を作成する場合、V8 は、元の文字列から関連するすべての文字をコピーするのではなく、 スライスされた文字列 オブジェクトを作成することを選択する場合があります。 この新しいオブジェクトには、元の文字列へのポインターが含まれており、元の文字列から使用する文字の範囲を記述します。 |
system / Context | 入れ子になった関数からアクセスできる JavaScript スコープのローカル変数。 すべての関数インスタンスには、それらの変数にアクセスできるように、実行されるコンテキストへの内部ポインターが含まれています。 |
(system) | まだ意味のある方法で分類されていないさまざまな内部オブジェクト。 |
比較ビュー
リークされたオブジェクトを見つけるには、複数のスナップショットを相互に比較します。 Web アプリケーションでは、通常、アクションを実行し、逆のアクションを実行すると、メモリ内のオブジェクトが増えるわけではありません。 たとえば、ドキュメントを開いて閉じる場合、メモリ内のオブジェクトの数はドキュメントを開く前と同じである必要があります。
特定の操作でリークが発生しないことを確認するには:
操作を実行する前に、ヒープスナップショットを取ります。
操作を実行します。 つまり、リークを引き起こす可能性のある何らかの方法でページと対話します。
逆操作を実行します。 つまり、反対の操作を行い、数回繰り返します。
2 つ目のヒープをスナップショットします。
2 番目のヒープ スナップショットで、ビューを [比較] に変更し、スナップショット 1 と比較します。
[比較] ビューには、2 つのスナップショットの違いが表示されます。
リスト内のコンストラクターを展開すると、追加および削除されたオブジェクト インスタンスが表示されます。
Containment ビュー
Containment ビューを使用すると、関数のクロージャ内をピークしたり、JavaScript オブジェクトを構成する仮想マシン (VM) 内部オブジェクトを観察したり、アプリケーションが非常に低いレベルで使用するメモリ量を把握したりできます。
Containment ビューには、次の種類のオブジェクトが表示されます。
Containment ビューのエントリ ポイント | 説明 |
---|---|
DOMWindow オブジェクト | JavaScript コードのグローバル オブジェクト。 |
GC ルート | JavaScript 仮想マシンのガベージ コレクターによって使用される GC ルート。 GC ルートは、組み込みのオブジェクト マップ、シンボル テーブル、VM スレッド スタック、コンパイル キャッシュ、ハンドル スコープ、およびグローバル ハンドルで構成されます。 |
ネイティブ オブジェクト | ブラウザーによって作成されたオブジェクト (DOM ノードや CSS ルールなど) は、自動化を許可するために JavaScript 仮想マシンに表示されます。 |
[Retainers]\(リテーナー\) セクション
[ リテーナー ] セクションが メモリ ツールの下部に表示され、選択したオブジェクトを指すすべてのオブジェクトが表示されます。 [概要]、[包含]、または [比較]ビューで別のオブジェクトを選択すると、[保持者] セクションが更新されます。
次のスクリーンショットでは、Summary ビューで文字列オブジェクトが選択されており、Retainers セクションは、ファイル内example-03.js
にあるクラスのインスタンスのItem
プロパティによってx
文字列が保持されていることを示しています。
サイクルを非表示にする
[ 保持者 ] セクションで、選択したオブジェクトを保持するオブジェクトを分析すると、サイクルが発生 する可能性があります。 サイクルは、選択したオブジェクトのリテーナー パスに同じオブジェクトが複数回表示される場合に発生します。 [ Retainers]\(リテーナー\ ) セクションでは、循環オブジェクトは淡色表示で示されます。
リテーナー パスを簡略化するには、[リ テーナー ] セクションで [ フィルター エッジ ] ドロップダウン メニューをクリックし、[サイクルを表示しない] を選択して 、サイクルを非表示にします。
内部ノードを非表示にする
内部ノード は、V8 (Microsoft Edge の JavaScript エンジン) に固有のオブジェクトです。
[リテーナー] セクションで内部ノードを非表示にするには、[フィルターエッジ] ドロップダウン メニューの [内部ノードを非表示にする] を選択します。
オブジェクトのサイズ全体を含むように [浅いサイズ] 列を構成する
既定では、メモリ ツールの [浅いサイズ] 列には、オブジェクト自体のサイズのみが含まれます。 浅いサイズは、オブジェクトによって直接保持される JavaScript ヒープのサイズです。 JavaScript オブジェクトは、オブジェクトの直接保持されたメモリに値ではなく、オブジェクトの説明のみを格納するため、通常、オブジェクトの浅いサイズは小さくなります。 ほとんどの JavaScript オブジェクトは、JavaScript ヒープ内の他の場所にある バッキング ストア に値を格納し、オブジェクトが直接所有する JavaScript ヒープの部分にのみ小さなラッパー オブジェクトを公開します。 たとえば、JavaScript Array
インスタンスは、配列の内容をバッキング ストアに格納します。これは、配列の浅いサイズに含まれていない別のメモリ位置です。
オブジェクトのバッキング ストアのサイズなど、オブジェクトのサイズ全体をレポートするように [浅い サイズ] 列を構成できます。
[浅いサイズ] 列にオブジェクトのサイズ全体を含める場合:
DevTools で、[ DevTools のカスタマイズと制御 ] ([DevTools の) ボタンをクリックし、[ 設定 ] () をクリックします。 または、DevTools にフォーカスがある場合は、 F1 キーを押します。
[ 実験 ] セクション で、[ヒープ スナップショット内] チェック ボックスをオンにし、バッキング ストア のサイズを含むオブジェクトの一部として扱います。
[設定] ページの [閉じる ] (x) ボタンをクリックし、[DevTools の再読み込み] ボタンをクリックします。
新しいヒープをスナップショットします。 [浅いサイズ] 列にオブジェクトのサイズ全体が含まれるようになりました。
ノードの種類別にヒープ スナップショットをフィルター処理する
フィルターを使用して、ヒープ スナップショットの特定の部分に焦点を当てます。 メモリ ツールでヒープ スナップショット内のすべてのオブジェクトを見ると、特定のオブジェクトに焦点を当てたり、パスを保持することが困難になる場合があります。
特定の種類のノードのみに焦点を当てるには、右上の [ノードの種類] フィルターを使用します。 たとえば、ヒープ内の配列と文字列オブジェクトのみを表示するには、次のスナップショット。
[ノードの種類] フィルターを開くには、右上の [既定値] をクリックします。
[配列] エントリと [文字列] エントリを選択します。
ヒープ スナップショットは、配列オブジェクトと文字列オブジェクトのみを表示するように更新されます。
特定のオブジェクトを検索する
収集されたヒープ内のオブジェクトを検索するには、 Ctrl + F キー を使用して検索し、オブジェクト ID を指定します。
DOM リークを検出する
メモリ ツールでは、ブラウザーのネイティブ オブジェクト (DOM ノード、CSS ルール) と JavaScript オブジェクトの間に存在する双方向の依存関係を表示できます。 これにより、メモリに残っている切断された DOM ノードが忘れられたために発生するメモリ リークを検出するのに役立ちます。
次の DOM ツリーについて考えてみましょう。
次のコード サンプルでは、ツリー内の DOM ノードの 2 つを参照する JavaScript 変数 treeRef
と leafRef
を作成します。
// Get a reference to the #tree element.
const treeRef = document.querySelector("#tree");
// Get a reference to the #leaf element,
// which is a descendant of the #tree element.
const leafRef = document.querySelector("#leaf");
次のコード サンプルでは、 <div id="tree">
要素が DOM ツリーから削除されます。
// Remove the #tree element from the DOM.
document.body.removeChild(treeRef);
<div id="tree">
JavaScript 変数treeRef
がまだ存在するため、要素をガベージ コレクションすることはできません。 変数は treeRef
要素を <div id="tree">
直接参照します。 次のコード サンプルでは、変数は treeRef
null 化されています。
// Remove the treeRef variable.
treeRef = null;
<div id="tree">
JavaScript 変数leafRef
がまだ存在するため、要素をガベージ コレクションすることはできません。 プロパティは leafRef.parentNode
、 要素を <div id="tree">
参照します。 次のコード サンプルでは、変数は leafRef
null 化されています。
// Remove the leafRef variable.
leafRef = null;
この時点で、 要素を <div id="tree">
ガベージ コレクションできます。 要素の下の DOM ツリー全体をガベージ コレクションするには、 と leafRef
の両方treeRef
を<div id="tree">
最初に null 化する必要があります。
デモ Web ページ: 例 6: DOM ノードのリーク
DOM ノードがリークする可能性がある場所と、そのようなリークを検出する方法を理解するには、Web ページ例 6: 新しいウィンドウまたはタブで DOM ノードをリークする 方法の例を開きます。
デモ Web ページ: 例 9: DOM リークが予想よりも大きい
DOM リークが予想以上に大きくなる理由を確認するには、例の Web ページ例 9: 新しいウィンドウまたはタブで DOM リークが予想よりも大きい を開きます。
クロージャがメモリに与える影響を分析する
メモリに対するクロージャの影響を分析するには、次の例を試してください。
新しいウィンドウまたはタブで Eval is evil デモ Web ページを開きます。
ヒープ スナップショットを記録します。
レンダリングされた Web ページで、[ クロージャと eval ] ボタンをクリックします。
2 つ目のヒープ スナップショットを記録します。
サイドバーでは、2 番目のスナップショットの下の数値は、最初のスナップショットの下の数値より大きくする必要があります。 これは、 eval ボタンを使用してクロージャをクリックした後、Web ページでより多くのメモリが使用されていることを示します。
2 番目のヒープ スナップショットで、ビューを [比較] に変更し、2 番目のヒープ スナップショットを最初のヒープ スナップショットと比較します。
比較ビューは、次の 2 番目のヒープ スナップショットで新しい文字列が作成されたことを示しています。
[比較] ビューで、(文字列) コンストラクターを展開します。
最初の (文字列) エントリをクリックします。
[ Retainers]\(リテーナー\ ) セクションが更新され、変数が
largeStr
[比較 ] ビューで選択された文字列を保持していることを示します。エントリは
largeStr
自動的に展開され、変数が関数によってeC
保持されることを示します。これは、変数が定義されているクロージャです。
ヒント: スナップショットのクロージャを区別する関数に名前を付ける
ヒープ スナップショット内の JavaScript クロージャを簡単に区別するには、関数名を指定します。
次の例では、名前のない関数を使用して変数を largeStr
返します。
function createLargeClosure() {
const largeStr = 'x'.repeat(1000000).toLowerCase();
// This function is unnamed.
const lC = function() {
return largeStr;
};
return lC;
}
次の例では、関数の名前を付けます。これにより、ヒープ スナップショット内のクロージャを区別しやすくなります。
function createLargeClosure() {
const largeStr = 'x'.repeat(1000000).toLowerCase();
// This function is named.
const lC = function lC() {
return largeStr;
};
return lC;
}
ヒープ スナップショットから JSON への文字列の保存とエクスポート
メモリ ツールでヒープ スナップショットを取得する場合は、スナップショットから JSON ファイルにすべての文字列オブジェクトをエクスポートできます。 メモリ ツールの [コンストラクター] セクションで、エントリの横にある [すべてファイルに保存] ボタンを(string)
クリックします。
メモリ ツールは、ヒープ スナップショットからすべての文字列オブジェクトを含む JSON ファイルをエクスポートします。
関連項目
- メモリ用語。
- Gonzalo Ruiz de Villa による Chrome DevTools を使用した JavaScript でのメモリ リークの検出とデバッグ。これは Microsoft Edge DevTools にも適用されます。
注:
このページの一部は、 Google によって 作成および共有され、 クリエイティブ・コモンズ属性 4.0 国際ライセンスに記載されている条件に従って使用される作業に基づく変更です。 元のページはこちらで、Meggin Kearney (Technical Writer) によって作成されています。