メモリ ツールを使用してヒープ スナップショットを記録する ("ヒープ スナップショット" プロファイルの種類)

メモリ ツールのヒープ プロファイラーを使用して、次の操作を行います。

  • JavaScript ヒープ (JS ヒープ) スナップショットを記録します。
  • メモリ グラフを分析します。
  • スナップショットを比較します。
  • メモリ リークを見つけます。

DevTools ヒープ プロファイラーは、JavaScript オブジェクトと、レンダリングされた Web ページ上の関連 DOM ノードによって使用されるメモリ分布を示します。

スナップショットを取得する

  1. 分析する Web ページを開きます。 たとえば、新しいウィンドウまたはタブで [散布図オブジェクト ] デモ ページを開きます。

  2. DevTools を開くには、Web ページを右クリックし、[ 検査] を選択します。 または、 Ctrl + Shift + I (Windows、Linux) または Command + Option + I (macOS) を押します。 DevTools が開きます。

  3. DevTools の アクティビティ バーで、[ メモリ ] タブを選択します。そのタブが表示されない場合は、[ その他のツール ] (その他のツール アイコン) ボタンをクリックします。

  4. [プロファイルの種類の選択] セクションで、[ヒープ スナップショット] オプション ボタンを選択します。

  5. [ JavaScript VM インスタンスの選択] で、プロファイリングする JavaScript VM を選択します。

  6. [スナップショットの取得] ボタンをクリックします。

[メモリ] ツールの [ヒープ スナップショット] オプションが選択され、[スナップショットの取得] ボタンが強調表示されています

新しく記録されたヒープ スナップショットが DevTools に読み込まれ、解析されると、スナップショットが表示され、[プロファイル] サイドバーの [ヒープ スナップショット] に新しいエントリが表示されます。

到達可能なオブジェクトの合計サイズ

新しいサイドバー項目の下の番号は、到達可能な JavaScript オブジェクトの合計サイズを示しています。 ヒープ スナップショットのオブジェクト サイズの詳細については、「メモリの用語」の「オブジェクトのサイズと距離」を参照してください。

スナップショットは、グローバル オブジェクトから到達可能なメモリ グラフからのオブジェクトのみを表示します。 スナップショットの取得は、常にガベージ コレクションから始まります。

別のスナップショットを取得する

メモリ ツールに既に表示されているときに別のスナップショットを取得するには、サイドバーで、既存のスナップショットの上にある [プロファイル] をクリックします。

別のスナップショットを取得するための [プロファイル] ボタン

スナップショットをクリアする

[メモリ] ツールからすべてのスナップショットをクリアするには、[すべてのプロファイルをクリアする] (クリア アイコン) アイコンをクリックします。

スナップショットを削除する

スナップショットの表示

ヒープ スナップショットは、 メモリ ツールで複数の異なる方法で表示できます。 UI でヒープ スナップショットを表示する各方法は、異なるタスクに対応します。

表示 コンテンツ 使用対象
概要 コンストラクター名でグループ化されたオブジェクトを表示します。 コンストラクター名でグループ化された型に基づいて、オブジェクトと使用するメモリを検索します。 DOM リークを追跡するのに役立ちます。
比較 2 つのスナップショットの違いを表示します。 操作の前後から 2 つ (またはそれ以上) のメモリ スナップショットを比較する。 解放されたメモリ内のデルタを検査し、参照カウントを検査すると、メモリ リークの存在と原因を確認し、その原因を特定するのに役立ちます。
コンテインメント ヒープの内容の探索を許可します。 オブジェクト構造をより適切に表示し、グローバル名前空間 (ウィンドウ) で参照されているオブジェクトを分析して、オブジェクトを保持している内容を調べるのに役立ちます。 クロージャを分析し、低レベルでオブジェクトを掘り下げるために使用します。

ビューを切り替えるには、 メモリ ツールの上部にあるドロップダウン リストを使用します。

ビューセレクターを切り替える

ヒープ スナップショットから省略された項目

ネイティブ コードを実行するゲッターを使用して実装されたプロパティは、JavaScript ヒープに格納されていないため、ヒープ スナップショットではキャプチャされません。

数値などの文字列以外の値はキャプチャされません。

概要ビュー

メモリ ツールの [概要] ビューには、次の一覧が表示されます。

  • オブジェクト コンストラクター グループ。
  • (配列)(コンパイル済みコード){foo、bar、baz}などのプロパティのリストなどの特殊なカテゴリ名。

最初に、ヒープ スナップショットが [概要] ビューで開き、コンストラクターの一覧が表示されます。

概要ビュー

リスト内の各コンストラクターを展開して、そのコンストラクターを使用してインスタンス化されたオブジェクトを表示できます。

一覧の各コンストラクターについて、 Summary ビューには、コンストラクターで作成されたオブジェクトの合計数を示す 、×123 などの数値も表示されます。 [概要] ビューには、次の列も表示されます。

列名 説明
Distance ノードの最短の単純パスを使用して、ルートまでの距離を表示します。 「メモリ内 の距離の用語を参照してください。
浅いサイズ 特定のコンストラクター関数によって作成されたすべてのオブジェクトの浅いサイズの合計を表示します。 浅いサイズは、オブジェクトによって直接保持される JavaScript ヒープのサイズです。 JavaScript オブジェクトは、オブジェクトの直接保持されたメモリに値ではなく、オブジェクトの説明のみを格納するため、通常、オブジェクトの浅いサイズは小さくなります。 ほとんどの JavaScript オブジェクトは、JavaScript ヒープ内の他の場所にある バッキング ストア に値を格納し、オブジェクトが直接所有する JavaScript ヒープの部分にのみ小さなラッパー オブジェクトを公開します。 「メモリの 浅いサイズの用語を参照してください。
保持サイズ 同じオブジェクト のセット間で保持される最大サイズを表示します。 オブジェクトが削除された後に解放できるメモリのサイズ (および依存オブジェクトに到達できなくなります) は、保持サイズと呼ばれます。 「メモリの用語」の「保持サイズ」を参照してください。

サマリー ビューでコンストラクターを展開すると、コンストラクターのすべてのインスタンスが表示されます。 インスタンスごとに、浅いサイズと保持されたサイズが対応する列に表示されます。 @文字の後の数値はオブジェクトの一意の ID であり、オブジェクトごとにヒープ スナップショットを比較できます。

コンストラクター エントリ

メモリ ツールの [概要] ビューには、オブジェクト コンストラクター グループが一覧表示されます。

コンストラクター グループ

Summary ビューのコンストラクター グループは、ArrayObjectなどの組み込み関数であるか、独自のコードで定義されている関数である可能性があります。

特定のコンストラクターによってインスタンス化されたオブジェクトの一覧を表示するには、コンストラクター グループを展開します。

特殊なカテゴリ名

メモリ ツールの概要ビューには、コンストラクターに基づいていない次の特殊なカテゴリ名が含まれています。 これらのカテゴリ名のほとんどは、かっこで囲んで表示されます。

カテゴリ名 説明
(配列) JavaScript 配列の内容や JavaScript オブジェクトの名前付きプロパティなど、JavaScript から表示されるオブジェクトに直接対応しない、さまざまな内部配列のようなオブジェクト。
(コンパイル済みコード) V8 (Microsoft Edge の JavaScript エンジン) が JavaScript または WebAssembly によって定義された関数を実行するために必要な内部データ。 V8 は、このカテゴリのメモリ使用量を自動的に管理します。関数が何度も実行される場合、V8 はその関数に対してより多くのメモリを使用して、関数の実行速度が速くなります。 関数がしばらく実行されていない場合、V8 はその関数の内部データを削除する可能性があります。
(連結された文字列) JavaScript + 演算子を使用する場合など、2 つの文字列が連結されている場合、V8 は結果を 連結された文字列として内部的に表す場合があります。 V8 は、2 つの文字列のすべての文字を新しい文字列にコピーするのではなく、2 つの文字列を指す小さなオブジェクトを作成します。
(オブジェクト図形) オブジェクトに関する情報(持つプロパティの数、プロトタイプへの参照など)、オブジェクトの作成時と更新時に V8 が内部的に保持します。 これにより、V8 は同じプロパティを持つオブジェクトを効率的に表すことができます。
(スライスされた文字列) JavaScript substring メソッドを使用する場合など、部分文字列を作成する場合、V8 は、元の文字列から関連するすべての文字をコピーするのではなく、 スライスされた文字列 オブジェクトを作成することを選択する場合があります。 この新しいオブジェクトには、元の文字列へのポインターが含まれており、元の文字列から使用する文字の範囲を記述します。
(system) まだ意味のある方法で分類されていないさまざまな内部オブジェクト。
{foo, bar, baz} インターフェイス (プロパティ リスト) で分類されたプレーンな JavaScript オブジェクトを中かっこで囲みます。 プレーン JavaScript オブジェクトは 、Object という名前のカテゴリには一覧表示されませんが、代わりに、 {foo、bar、baz} などのオブジェクトに含まれるプロパティに基づく名前とカテゴリで表されます。
InternalNode V8 の外部に割り当てられたオブジェクト (Microsoft Edge のレンダリング エンジン Blink によって定義された C++ オブジェクトなど)。
system / Context 入れ子になった関数からアクセスできる JavaScript スコープのローカル変数。 すべての関数インスタンスには、それらの変数にアクセスできるように、実行されるコンテキストへの内部ポインターが含まれています。

比較ビュー

リークされたオブジェクトを見つけるには、複数のスナップショットを相互に比較します。 Web アプリケーションでは、通常、アクションを実行し、逆のアクションを実行すると、メモリ内のオブジェクトが増えるわけではありません。 たとえば、ドキュメントを開いて閉じる場合、メモリ内のオブジェクトの数はドキュメントを開く前と同じである必要があります。

特定の操作でリークが発生しないことを確認するには:

  1. 操作を実行する前に、ヒープスナップショットを取ります。

  2. 操作を実行します。 つまり、リークを引き起こす可能性のある何らかの方法でページと対話します。

  3. 逆操作を実行します。 つまり、反対の操作を行い、数回繰り返します。

  4. 2 つ目のヒープをスナップショットします。

  5. 2 番目のヒープ スナップショットで、ビューを [比較] に変更し、スナップショット 1 と比較します。

[比較] ビューには、2 つのスナップショットの違いが表示されます。

比較ビュー

リスト内のコンストラクターを展開すると、追加および削除されたオブジェクト インスタンスが表示されます。

Containment ビュー

Containment ビューを使用すると、関数のクロージャ内をピークしたり、JavaScript オブジェクトを構成する仮想マシン (VM) 内部オブジェクトを観察したり、アプリケーションが非常に低いレベルで使用するメモリ量を把握したりできます。

Containment ビュー

Containment ビューには、次の種類のオブジェクトが表示されます。

Containment ビューのエントリ ポイント 説明
DOMWindow オブジェクト JavaScript コードのグローバル オブジェクト。
GC ルート JavaScript 仮想マシンのガベージ コレクターによって使用される GC ルート。 GC ルートは、組み込みのオブジェクト マップ、シンボル テーブル、VM スレッド スタック、コンパイル キャッシュ、ハンドル スコープ、およびグローバル ハンドルで構成されます。
ネイティブ オブジェクト ブラウザーによって作成されたオブジェクト (DOM ノードや CSS ルールなど) は、自動化を許可するために JavaScript 仮想マシンに表示されます。

[Retainers]\(リテーナー\) セクション

[ リテーナー ] セクションが メモリ ツールの下部に表示され、選択したオブジェクトを指すすべてのオブジェクトが表示されます。 [概要]、[包含]、または [比較]ビューで別のオブジェクトを選択すると、[保持者] セクションが更新されます。

次のスクリーンショットでは、Summary ビューで文字列オブジェクトが選択されており、Retainers セクションは、example-03.js ファイルにあるItem クラスのインスタンスのx プロパティによって文字列が保持されていることを示しています。

[Retainers]\(リテーナー\) セクション

サイクルを非表示にする

[ 保持者 ] セクションで、選択したオブジェクトを保持するオブジェクトを分析すると、サイクルが発生 する可能性があります。 サイクルは、選択したオブジェクトのリテーナー パスに同じオブジェクトが複数回表示される場合に発生します。 [ Retainers]\(リテーナー\ ) セクションでは、循環オブジェクトは淡色表示で示されます。

リテーナー パスを簡略化するには、[リ テーナー ] セクションで [ フィルター エッジ ] ドロップダウン メニューをクリックし、[サイクルを表示しない] を選択して 、サイクルを非表示にします

[リテーナー] セクションの [フィルター エッジ] ドロップダウン メニューの [サイクルを非表示にする] が選択されています

内部ノードを非表示にする

内部ノード は、V8 (Microsoft Edge の JavaScript エンジン) に固有のオブジェクトです。

[リテーナー] セクションで内部ノードを非表示にするには、[フィルターエッジ] ドロップダウン メニューの [内部ノードを非表示にする] を選択します。

ノードの種類別にヒープ スナップショットをフィルター処理する

フィルターを使用して、ヒープ スナップショットの特定の部分に焦点を当てます。 メモリ ツールでヒープ スナップショット内のすべてのオブジェクトを見ると、特定のオブジェクトに焦点を当てたり、パスを保持することが困難になる場合があります。

特定の種類のノードのみに焦点を当てるには、右上の [ノードの種類] フィルターを使用します。 たとえば、ヒープ内の配列と文字列オブジェクトのみを表示するには、次のスナップショット。

  1. [ノードの種類] フィルターを開くには、右上の [既定値] をクリックします。

  2. [配列] エントリと [文字列] エントリを選択します。

    ヒープ スナップショットは、配列オブジェクトと文字列オブジェクトのみを表示するように更新されます。

    メモリ ツールのヒープ スナップショット内のノードの種類

特定のオブジェクトを検索する

収集されたヒープ内のオブジェクトを検索するには、 Ctrl + F キー を使用して検索し、オブジェクト ID を指定します。

DOM リークを検出する

メモリ ツールでは、ブラウザーのネイティブ オブジェクト (DOM ノード、CSS ルール) と JavaScript オブジェクトの間に存在する双方向の依存関係を表示できます。 これにより、メモリに残っている DOM ノードが忘れられ、切断されたために発生するメモリ リークを検出するのに役立ちます。

デタッチされた要素については、以下の「DOM ツリー のメモリ リークの検索 ("ヒープ スナップショット" プロファイリングの種類 > Detached)」も参照してください。

次の DOM ツリーについて考えてみましょう。

DOM サブツリー

次のコード サンプルでは、ツリー内の 2 つの DOM ノードを参照する JavaScript 変数 treeRefleafRefを作成します。

// 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);

javaScript 変数treeRefがまだ存在するため、<div id="tree">要素はガベージ コレクションできません。 treeRef変数は、<div id="tree">要素を直接参照します。 次のコード サンプルでは、 treeRef 変数は null になります。

// Remove the treeRef variable.
treeRef = null;

JavaScript 変数leafRefがまだ存在するため、<div id="tree">要素はガベージ コレクションできません。 leafRef.parentNode プロパティは、<div id="tree"> 要素を参照します。 次のコード サンプルでは、 leafRef 変数は null になります。

// Remove the leafRef variable.
leafRef = null;

この時点で、 <div id="tree"> 要素をガベージ コレクションできます。 <div id="tree">要素の下の DOM ツリー全体をガベージ コレクションするには、treeRefleafRefの両方を最初に null 化する必要があります。

デモ Web ページ: 例 6: DOM ノードのリーク

DOM ノードがリークする可能性がある場所と、そのようなリークを検出する方法を理解するには、Web ページ例 6: 新しいウィンドウまたはタブで DOM ノードをリークする 方法の例を開きます。

デモ Web ページ: 例 9: DOM リークが予想よりも大きい

DOM リークが予想以上に大きくなる理由を確認するには、例の Web ページ例 9: 新しいウィンドウまたはタブで DOM リークが予想よりも大きい を開きます。

クロージャがメモリに与える影響を分析する

メモリに対するクロージャの影響を分析するには、次の例を試してください。

  1. 新しいウィンドウまたはタブで Eval is evil デモ Web ページを開きます。

  2. ヒープ スナップショットを記録します。

  3. レンダリングされた Web ページで、[ クロージャと eval ] ボタンをクリックします。

  4. 2 つ目のヒープ スナップショットを記録します。

    サイドバーでは、2 番目のスナップショットの下の数値は、最初のスナップショットの下の数値より大きくする必要があります。 これは、 eval ボタンを使用してクロージャをクリックした後、Web ページでより多くのメモリが使用されていることを示します。

  5. 2 番目のヒープ スナップショットで、ビューを [比較] に変更し、2 番目のヒープ スナップショットを最初のヒープ スナップショットと比較します。

    比較ビューは、次の 2 番目のヒープ スナップショットで新しい文字列が作成されたことを示しています。

    2 番目のスナップショットで新しい文字列が作成されたことを示す [比較] ビュー

  6. [比較] ビューで、(文字列) コンストラクターを展開します。

  7. 最初の (文字列) エントリをクリックします。

    [ Retainers ] セクションが更新され、 largeStr 変数が 比較 ビューで選択した文字列を保持していることを示します。

    largeStrエントリは自動的に展開され、変数が定義されているクロージャであるeC関数によって変数が保持されることを示します。

    文字列が eC 関数によって保持されていることを示す [Retainers] セクション

ヒント: スナップショットのクロージャを区別する関数に名前を付ける

ヒープ スナップショット内の 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 にすべての文字列を保存する

メモリ ツールは、ヒープ スナップショットからすべての文字列オブジェクトを含む JSON ファイルをエクスポートします。

JSON ファイル内のヒープ スナップショットからの文字列

DOM ツリーのメモリ リークを検索する ("ヒープ スナップショット" プロファイリングの種類 > Detached)

Web ページ上のすべてのデタッチされた要素を検索して表示する方法の 1 つは、メモリ ツールのヒープ スナップショット プロファイルの種類を使用し、次のように [クラスでフィルター処理] テキスト ボックスに「Detached」と入力することです。 「メモリの問題を解決する」の「デタッチされた要素を調査するためのツール」も参照してください。

次のコードでは、デタッチされた DOM ノードが生成されます。

var detachedTree;

function create() {
    var ul = document.createElement('ul');
    for (var i = 0; i < 10; i++) {
        var li = document.createElement('li');
        ul.appendChild(li);
    }
    detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);

このコードでは、10 liの子を持つul ノードを作成します。 ノードはコードによって参照されますが、DOM ツリーには存在しないため、各ノードはデタッチされます。

ヒープ スナップショットは、デタッチされたノードを識別する 1 つの方法です。 ヒープ スナップショットは、スナップショットの時点でページの JS オブジェクトと DOM ノード間でメモリがどのように分散されるかを示します。

"Heap スナップショット" プロファイルの種類を使用してデタッチされた要素を検索する

ヒープ スナップショット プロファイルの種類を使用してデタッチされた要素を検索するには:

  1. デタッチされた要素のデモ Web ページなどの Web ページを新しいウィンドウまたはタブで開きます。

  2. Web ページを右クリックし、[ 検査] を選択します。 または、 Ctrl + Shift + I (Windows、Linux) または Command + Option + I (macOS) を押します。

    DevTools が開きます。

  3. DevTools の アクティビティ バーで、 メモリ (メモリ ツール アイコン) ツールを選択します。

    そのタブが表示されない場合は、[ その他のツール ] ([その他のツール] アイコン) ボタンをクリックし、[ メモリ] を選択します。 メモリ ツールが開きます。

    メモリ ツールを開く

    [ヒープ スナップショット オプション] ボタンが表示されていない場合は、プロファイルが既に表示されているため、左上の [プロファイル] ([プロファイル] アイコン) をクリックします。

    Web ページでデタッチされた要素がまだ生成されていないため、この時点で [ヒープ スナップショット] オプション ボタンを選択する必要はありません。

    Room クラスの JavaScript インスタンスによって格納されるメッセージを生成します。

  4. デモ Web ページで、[ 高速トラフィック ] ボタンをクリックします。

    デモ Web ページでは、メッセージの生成と Web ページへの表示が開始されます。

    デモ Web ページでのメッセージの生成

  5. 一部のメッセージが表示されたら、デモ Web ページの [停止 ] ボタンをクリックします。

    各メッセージは、Room クラスの Room 1 インスタンスによって参照される<div class="message">要素です。 Web ページ DOM ツリーにはデタッチされた要素はありません。すべてのメッセージ要素が Room クラスの現在の Room 1 インスタンスにアタッチされているためです。

    Room クラスの別のインスタンスに変更すると、要素がデタッチされます。

  6. デモ Web ページで、Room クラスの別のインスタンスに対応する [会議室 2] ボタンをクリックします。

    Web ページでは、メッセージは消えます。

    ルーム 2 イニシャル ビュー

    Room クラス (<div class="message"> 要素) の Room 1 インスタンスに対して生成されたメッセージは DOM にアタッチされなくなりましたが、Room クラスの Room 1 インスタンスによって引き続き参照されます。 これらはデタッチされた要素であり、Web ページで再度使用しない限り、メモリ リークを引き起こす可能性があります。

    デタッチされた要素の一覧を取得します。

  7. DevTools の メモリ ツールで、[ ガベージの収集 ] ([ガベージの収集] アイコン) アイコンをクリックします。

    ヒープのスナップショット

    ブラウザーはガベージ コレクションを実行し、JavaScript オブジェクトによって参照されなくなったノードを削除します。

  8. [メモリ] ツールで、[ヒープ スナップショット] オプション ボタンを選択します。

  9. メモリ ツールの下部にある [スナップショットの取得] ボタンをクリックします。

    スナップショットが処理され、読み込まれ、[プロファイル] サイドバーの [ヒープ スナップショット] セクションに一覧表示されます。

  10. [ クラスでフィルター処理 ] テキスト ボックスに、「 detached」と入力します。

    デタッチされたノードのフィルター処理とデタッチされたノードの展開

    ガベージ コレクションできないデタッチされた DOM 要素が表示されます。

    特定のデタッチされた要素を参照する JavaScript コードを特定します。

  11. ヒープ スナップショットで、Detached オブジェクト (Detached <div> など) を展開し、[Detached <div class="message"> ノードを選択します。

    情報は、メモリ ツールの下部にある [リテーナー] ウィンドウに表示されます。

  12. [リテーナー] ウィンドウで、[配列] の下の [Room] 項目でマウントされていないroom.js:13リンクをクリックします。 [ソース] ツールが開き、room.jsが表示され、13 行目までスクロールされます。

    ソース ツールの JavaScript ソース コード

  13. メモリ リークの可能性を調べるには、 unmounted 配列を使用するコードを調べ、ノードへの参照が不要になったときに削除されていることを確認します。

  14. メモリ ツールに戻すには、アドレス バーで [メモリ] ツールを選択します。

デタッチされた要素を確認するその他の方法については、「メモリの問題を修正する」の「デタッチされた要素を調査するためのツール」を参照してください。

関連項目

外:

注:

このページの一部は、 Google によって 作成および共有され、 クリエイティブ・コモンズ属性 4.0 国際ライセンスに記載されている条件に従って使用される作業に基づく変更です。 元のページは ここに あり、Meggin Kearney によって作成されています。

クリエイティブ・コモンズ・ライセンス この作品は 、クリエイティブ・コモンズ属性4.0国際ライセンスに基づきライセンスされています