メモリの問題を修正する

Microsoft Edge と DevTools を使用して、メモリ リーク、メモリの膨らみ、頻繁なガベージ コレクションなど、ページのパフォーマンスに影響するメモリの問題を見つける方法について説明します。

  • Microsoft Edge ブラウザー タスク マネージャーでページが現在使用しているメモリ量を確認します。
  • メモリ ツールを使用して、時間の経過に伴う メモリ 使用量を視覚化します。
  • ヒープ スナップショットを使用して、デタッチされた DOM ツリー (メモリ リークの一般的な原因) を特定します。
  • タイムラインの割り当てインストルメンテーションを使用して、JavaScript ヒープ (JS ヒープ) に新しいメモリが割り当てられているタイミングを確認します。

デタッチされた要素ツールを使用して DOM メモリ リークをデバッグする」も参照してください。

概要

RAIL パフォーマンス モデルの精神では、パフォーマンスの取り組みの焦点はユーザーである必要があります。

メモリの問題は、多くの場合、ユーザーが認識できるため重要です。 ユーザーは、次の方法でメモリの問題を認識する可能性があります。

  • ページのパフォーマンスは、時間の経過と同時に徐々に悪化します。 これは、メモリ リークの症状である可能性があります。 メモリ リークは、ページのバグによって、ページが時間の経過と同時に徐々に多くのメモリを使用する場合です。

  • ページのパフォーマンスは一貫して悪いです。 これは、メモリの膨らみの症状である可能性があります。 メモリの膨らみは、ページが最適なページ速度のために必要以上に多くのメモリを使用する場合です。

  • ページのパフォーマンスが遅れているか、頻繁に一時停止しているように見えます。 これは、ガベージ コレクションが頻繁に発生する可能性があります。 ガベージ コレクションは、ブラウザーがメモリを解放する場合です。 この問題が発生するタイミングは、ブラウザーによって決定されます。 コレクション中に、実行中のすべてのスクリプトが一時停止されます。 そのため、ブラウザーが大量のガベージ コレクションを行っている場合、スクリプト ランタイムは大量に一時停止されます。

メモリの膨らみ: "多すぎる" 量はどのくらいですか?

メモリ リークは簡単に定義できます。 サイトがますます多くのメモリを使用している場合は、リークが発生します。 しかし、メモリの膨らみは、ピン留めするのが少し難しいです。 "メモリの使用量が多すぎる" と見なされるものは何ですか?

デバイスとブラウザーの機能が異なるため、ここではハードナンバーはありません。 ハイエンドスマートフォンでスムーズに動作する同じページは、ローエンドスマートフォンでクラッシュする可能性があります。

ここで重要なのは、RAIL モデルを使用し、ユーザーに焦点を当てることです。 ユーザーに人気のあるデバイスを確認し、それらのデバイスでページをテストします。 エクスペリエンスが一貫して悪い場合は、ページがそれらのデバイスのメモリ機能を超える可能性があります。

Microsoft Edge ブラウザー タスク マネージャーを使用してメモリ使用量をリアルタイムで監視する

メモリの問題調査の出発点として、Microsoft Edge ブラウザー タスク マネージャーを使用します。 Microsoft Edge ブラウザー タスク マネージャーは、ページが現在使用しているメモリ量を示すリアルタイム モニターです。

  1. Shift キーを押しながら Esc キーを押すか、Microsoft Edge メイン メニューに移動し、[その他のツール>] [ブラウザー タスク マネージャー] を選択して Microsoft Edge ブラウザー タスク マネージャーを開きます。

    Microsoft Edge ブラウザー タスク マネージャーを開く

  2. Microsoft Edge ブラウザー タスク マネージャーのテーブル ヘッダーを右クリックし、 JavaScript メモリを有効にします。

    JavaScript メモリの有効化

これらの 2 つの列は、ページでメモリがどのように使用されているかについて、さまざまなことを示します。

  • [ メモリ ] 列はネイティブ メモリを表します。 DOM ノードはネイティブ メモリに格納されます。 この値が増加している場合、DOM ノードが作成されます。

  • JavaScript メモリ列は JS ヒープを表します。 この列には、2 つの値が含まれています。 関心のある値は、ライブ番号 (かっこ内の数値) です。 ライブ番号は、ページ上の到達可能なオブジェクトが使用しているメモリ量を表します。 この数が増えている場合は、新しいオブジェクトが作成されているか、既存のオブジェクトが増加しています。

パフォーマンス ツールを使用してメモリ リークを視覚化する

また、調査の別の出発点として パフォーマンス ツールを使用することもできます。 パフォーマンス ツールを使用すると、時間の経過に伴うページのメモリ使用量を視覚化できます。

  1. DevTools で、 パフォーマンス ツールを開きます。

  2. [ メモリ ] チェック ボックスをオンにします。

  3. 録音を行います

強制ガベージ コレクションを使用して記録を開始して終了することをお勧めします。 ガベージ コレクションを強制するには、記録中に [ ガベージフォース ガベージ コレクション の収集] ボタンをクリックします。

メモリ記録を示すには、次のコードを検討してください。

var x = [];
function grow() {
    for (var i = 0; i < 10000; i++) {
        document.body.appendChild(document.createElement('div'));
    }
    x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);

コードで参照されているボタンがクリックされるたびに、10,000 div 個のノードがドキュメント本文に追加され、1,000,000 x 文字の文字列が配列に x プッシュされます。 前のコード サンプルを実行すると、次の図のように パフォーマンス ツールで記録が生成されます。

単純な成長

まず、ユーザー インターフェイスの説明を示します。 [概要] ペイン (NET の下) の HEAP グラフは、JS ヒープを表します。 [ 概要 ] ウィンドウの下に [ カウンター ] ペインがあります。 メモリ使用量は、JS ヒープ ([概要] ペインの HEAP グラフと同じ)、ドキュメント、DOM ノード、リスナー、GPU メモリによって分割されます。 チェック ボックスをオフにして、グラフから非表示にします。

次に、前の図と比較したコードの分析を示します。 ノード カウンター (緑のグラフ) を確認すると、コードときれいに一致します。 ノード数は、個別のステップで増加します。 ノード数の各増加は への呼び出し grow()であると推測できます。

JS ヒープ グラフ (青いグラフ) は、単純ではありません。 ベスト プラクティスに従って、最初のディップは実際には強制ガベージ コレクションです ([ ガベージフォース ガベージ コレクション の収集] ボタンをクリックします)。

記録が進むと、JS ヒープ サイズのスパイクが表示されます。 これは自然で想定されています。JavaScript コードでは、クリックするすべてのボタンに DOM ノードが作成され、100 万文字の文字列を作成するときに多くの作業が行われます。

ここで重要なのは、JS ヒープが開始されたよりも大きく終了するという事実です (ここでの "開始" は、強制ガベージ コレクションの後のポイントです)。 実際には、JS ヒープ サイズまたはノード サイズを増やすこのパターンを見た場合、メモリ リークを示す可能性があります。

ヒープ スナップショットを使用してデタッチされた DOM ツリーのメモリ リークを検出する

DOM ノードは、ページで実行されている DOM ツリーまたは JavaScript コードからのノードへの参照がない場合にのみガベージ コレクションされます。 ノードは DOM ツリーから削除されると "デタッチ" されると言われますが、一部の JavaScript では引き続き参照されます。 デタッチされた DOM ノードは、メモリ リークの一般的な原因です。

このセクションでは、DevTools のヒープ プロファイラーを使用してデタッチされたノードを識別する方法について説明します。

デタッチされた 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 ノード間でメモリがどのように分散されるかを示します。

スナップショットを作成するには:

  1. DevTools を開き、 メモリ ツールに移動します。

  2. [ヒープ スナップショット] ラジオ ボタンをクリックし、ツールの下部にある [スナップショットの取得] ボタンをクリックします。

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

    スナップショットの処理と読み込みに時間がかかる場合があります。

  3. スナップショットが完了したら、左側のパネルから選択します (HEAP SNAPSHOTS という名前です)。

  4. [ クラス フィルター ] テキスト ボックスに、「」と入力 Detachedして、デタッチされた DOM ツリーを検索します。

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

  5. カラットを展開して、デタッチされたツリーを調査します。

    デタッチされたツリーの調査

  6. ノードをクリックしてさらに調査します。

    [ オブジェクト ] ウィンドウで、ノードを参照しているコードの詳細を確認できます。 たとえば、次の図では、変数が detachedTree ノードを参照しています。

  7. 特定のメモリ リークを修正するには、変数を使用するコードを detachedTree 調査し、ノードへの参照が不要になったときに削除されていることを確認します。

ノードの調査

タイムラインの割り当てインストルメンテーションを使用して JS ヒープ メモリ リークを特定する

タイムラインの割り当てインストルメンテーションは、JS ヒープ内のメモリ リークを追跡するのに役立つもう 1 つのツールです。

次のコードを使用して、タイムラインでの割り当てインストルメンテーションを示します。

var x = [];
function grow() {
    x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);

コードで参照されているボタンがクリックされるたびに、配列に 100 万文字の文字列が追加 x されます。

タイムラインに割り当てインストルメンテーションを記録するには:

  1. DevTools を開き、[ メモリ ] ツールを選択します。

  2. [タイムライン割り当てインストルメンテーション] ラジオ ボタンをクリックし、[スタート] ボタンをクリックします。

  3. メモリ リークの原因と思われるアクションを実行します。

  4. 完了したら、[ヒープの記録を停止する] プロファイル[記録の停止] ボタンをクリックします。

  5. 記録中に、次の図のように、タイムラインの割り当てインストルメンテーションに青いバーが表示されるかどうかを確認します。

    新しい割り当て

    これらの青いバーは、新しいメモリ割り当てを表します。 これらの新しいメモリ割り当ては、メモリ リークの候補です。

  6. バーを拡大して、[ コンストラクター ] ウィンドウをフィルター処理して、指定した期間内に割り当てられたオブジェクトのみを表示します。

    ズームされた割り当てタイムライン

  7. オブジェクトを展開し、値を選択して[ オブジェクト ]ペインに詳細を表示します。

    たとえば、次の図では、新しく割り当てられたオブジェクトの詳細で、スコープ内Windowの変数にx割り当てられたことを示しています。

オブジェクトの詳細

関数によるメモリ割り当てを調査する

割り当てサンプリング プロファイルの種類を使用して、JavaScript 関数によるメモリ割り当てを表示します。

レコード割り当てサンプリング

  1. [ 割り当てサンプリング ] ラジオ ボタンをクリックします。

  2. ページにワーカーがある場合は、[ スタート ] ボタンの横にあるドロップダウン メニューを使用して、プロファイル ターゲットとして選択できます。

  3. [ スタート ] ボタンをクリックします。

  4. Web ページで、調査するアクションを実行します。

  5. すべてのアクションが完了したら、[ 停止 ] ボタンをクリックします。

DevTools には、関数別のメモリ割り当ての内訳が表示されます。 既定のビューは Heavy (Bottom Up) で、最も多くのメモリを割り当てた関数が上部に表示されます。

割り当てサンプリング

割り当てサンプリングの追加設定でガベージを削減する

既定では、 割り当てサンプリング プロファイルの種類では、記録セッションの最後にまだ有効な割り当てのみが報告されます。 作成、削除、ガベージ コレクション (GC'd) されたオブジェクトは、タイムライン型の割り当てサンプリングまたは割り当てインストルメンテーションを使用してプロファイリングを行うときにメモリ ツールに表示されません。

ブラウザーを信頼して、コードからガベージをクリーンできます。 ただし、GC 自体は高価な操作であり、複数の GC によってユーザーの Web サイトやアプリのエクスペリエンスが低下する可能性があることを考慮することが重要です。 パフォーマンス ツールで [メモリ] チェック ボックスをオンにして記録すると、ヒープ チャートの急な崖 (急激な減少) で GC 操作が発生することがわかります。

パフォーマンス ツールに表示される GC 操作

コードが作成するガベージの量を減らすことで、個々の GC のコストと GC 操作の数を削減できます。 GC によって破棄されたオブジェクトを追跡するには、 割り当てサンプリング プロファイルの種類を設定で構成します。

  1. [ 割り当てサンプリング ] オプション ボタンをクリックします。

  2. [ メジャー GC によって破棄されたオブジェクトを含める ] と [ マイナー GC 設定によって破棄されたオブジェクトを含める] をクリックします。

    割り当てサンプリング GC 設定

  3. [ スタート ] ボタンをクリックします。

  4. Web ページで、調査するアクションを実行します。

  5. すべてのアクションが完了したら、[ 停止 ] ボタンをクリックします。

DevTools は、記録中に GC'd だったすべてのオブジェクトを追跡するようになりました。 これらの設定を使用して、Web サイトまたはアプリが生成しているごみの量を把握します。 割り当てサンプリングによって報告されるデータは、最もガベージを生成している関数を識別するのに役立ちます。

特定のメジャーまたはマイナー GC 操作中に GC'd のみであったオブジェクトを調査する場合は、必要な操作を追跡するように設定を適切に構成します。 メジャー GC とマイナー GC の違いの詳細については、「 ごみ箱の話: Orinoco ガベージ コレクター |V8 JavaScript エンジン開発者ブログ

頻繁にガベージ コレクションを見つける

ページが頻繁に一時停止しているように見える場合は、ガベージ コレクションの問題が発生する可能性があります。

Microsoft Edge ブラウザー タスク マネージャーまたはパフォーマンス メモリ記録を使用して、頻繁にガベージ コレクションを見つけることができます。

  • Microsoft Edge ブラウザー タスク マネージャーでは、 メモリ または JavaScript メモリ の値が頻繁に上昇および低下し、頻繁にガベージ コレクションが表されます。

  • パフォーマンス記録では、JS ヒープまたはノード数グラフに対する頻繁な変更 (上昇と下降) は、ガベージ コレクションが頻繁に発生することを示します。

問題を特定したら、タイムライン記録で割り当てインストルメンテーションを使用して、メモリが割り当てられている場所と、割り当ての原因となっている関数を確認できます。

このページの一部の情報は、Google によって作成および共有されている著作物に基づいており、Creative Commons Attribution 4.0 International License に記載されている条項に従って使用されています。 元のページは ここに あり、 Kayce Basques (テクニカル ライター、Chrome DevTools & Lighthouse) によって作成されています。

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