このセクションでは、メモリ分析で使用される一般的な用語について説明します。これは、さまざまな言語のさまざまなメモリ プロファイリング ツールに適用されます。
ここで説明する用語と概念は、Chrome DevTools ヒープ プロファイラに関するものです。Java、.NET、またはその他のメモリ プロファイラを使用したことがある場合は、このセクションを復習してください。
オブジェクトのサイズ
メモリは、プリミティブ タイプ(数値や文字列など)とオブジェクト(連想配列)を含むグラフと考えてください。次のように、相互接続された複数の点を持つグラフとして視覚的に表すことができます。
オブジェクトは、次の 2 つの方法でメモリを保持できます。
- オブジェクト自体によって直接。
- 暗黙的に、他のオブジェクトへの参照を保持し、それらのオブジェクトがガベージ コレクタ(略称 GC)によって自動的に破棄されないようにします。
DevTools のヒープ プロファイラ(メモリパネルで見つかったメモリの問題を調査するためのツール)を使用すると、いくつかの異なる列の情報が表示されます。特に注目すべきは、[Shallow Size] と [Retained Size] です。これらはどのような意味ですか?
浅いサイズ
これは、オブジェクト自体が保持するメモリのサイズです。
一般的な JavaScript オブジェクトには、説明と即時値の保存用に予約されたメモリがあります。通常、浅いサイズが大きい可能性があるのは配列と文字列のみです。ただし、文字列と外部配列のメイン ストレージはレンダラ メモリにあることが多く、JavaScript ヒープには小さなラッパー オブジェクトのみが公開されます。
レンダラ メモリは、インスペクション対象のページがレンダリングされるプロセスのすべてのメモリです。ネイティブ メモリ、ページの JS ヒープメモリ、ページによって開始されたすべての専用ワーカーの JS ヒープメモリです。ただし、小さなオブジェクトでも、自動ガベージ コレクション プロセスによって他のオブジェクトが破棄されないようにすることで、間接的に大量のメモリを保持できます。
保持サイズ
これは、オブジェクト自体が削除されたときに、GC ルートから到達できなくなった依存オブジェクトとともに解放されるメモリのサイズです。
GC ルートは、ネイティブ コードから V8 外の JavaScript オブジェクトを参照するときに作成される(ローカルまたはグローバルの)ハンドルで構成されます。このようなハンドルはすべて、ヒープ スナップショットの GC roots > Handle scope と GC roots > Global handles で確認できます。このドキュメントでは、ブラウザの実装の詳細に立ち入らずにハンドルについて説明します。GC ルートやハンドルは、心配する必要はありません。
内部 GC ルートがたくさんありますが、そのほとんどはユーザーにとって興味がありません。アプリケーションの観点から、ルートには次の種類があります。
- ウィンドウのグローバル オブジェクト(各 iframe 内)。ヒープ スナップショットには、ウィンドウからの最短保持パス上のプロパティ参照数である距離フィールドがあります。
- ドキュメントを走査して到達できるすべてのネイティブ DOM ノードからなるドキュメント DOM ツリー。すべての要素に JS ラッパーが存在するとは限りません。存在する場合、ラッパーはドキュメントが存続している間存続します。
- オブジェクトがデバッガ コンテキストと DevTools コンソールで保持されることがあります(コンソール評価後など)。コンソールをクリアし、デバッガでアクティブなブレークポイントがない状態でヒープ スナップショットを作成します。
メモリグラフはルートから始まります。ルートは、ブラウザの window
オブジェクトまたは Node.js モジュールの Global
オブジェクトです。このルート オブジェクトの GC 方法は制御できません。
ルートから到達できないものはすべて GC されます。
オブジェクト保持ツリー
ヒープは、相互接続されたオブジェクトのネットワークです。数学の世界では、この構造はグラフまたはメモリグラフと呼ばれます。グラフは、エッジで接続されたノードから構成され、どちらにもラベルが付けられます。
- ノード(オブジェクト)には、作成に使用されたコンストラクタ関数の名前を使用してラベルが付けられます。
- エッジには、プロパティの名前を使用してラベルが付けられます。
Heap Profiler を使用してプロファイルを記録する方法を学習する。次のヒープ プロファイラ レコーディングで注目すべき点の一部は、距離(GC ルートからの距離)です。同じタイプのオブジェクトのほとんどが同じ距離にあり、一部のオブジェクトがより遠くにある場合は、調査する価値があります。
ドミネーター
各オブジェクトには 1 つのドミナントしかないため、ドミナント オブジェクトはツリー構造で構成されています。オブジェクトのドーマインには、そのドーメインが支配するオブジェクトへの直接参照がない場合があります。つまり、ドーマインのツリーはグラフのスパニング ツリーではありません。
以下の図で:
- ノード 1 がノード 2 を優先する
- ノード 2 がノード 3、4、6 をドミネートしている
- ノード 3 がノード 5 を支配している
- ノード 5 がノード 8 を優先する
- ノード 6 がノード 7 を優先する
次の例では、ノード #3
は #10
のドミナントですが、#7
は GC から #10
へのすべての単純なパスにも存在します。したがって、オブジェクト B がルートからオブジェクト A へのすべての単純なパスに存在する場合、オブジェクト B はオブジェクト A の支配者です。
V8 の詳細
メモリのプロファイリングを行う場合は、ヒープ スナップショットが特定の状態になっている理由を理解することが役立ちます。このセクションでは、V8 JavaScript 仮想マシン(V8 VM または VM)に固有のメモリ関連のトピックについて説明します。
JavaScript オブジェクトの表現
プリミティブ型には次の 3 つがあります。
- 数字(例: 3.14159..)
- ブール値(true または false)
- 文字列(例: 'Werner Heisenberg')
他の値を参照することはできず、常にリーフまたは終端ノードです。
数値は次のいずれかとして保存できます。
- 小数点以下の整数(SMI)と呼ばれる 31 ビットのイミディエイト整数値。
- ヒープ オブジェクト(ヒープ番号)。ヒープ数は、ダブルなど、SMI 形式に収まらない値を保存する場合や、値にプロパティを設定するなど、値をボックス化する必要がある場合に使用されます。
文字列は、次のいずれかに保存できます。
- VM ヒープ
- 外部(レンダラのメモリ)に保存されます。ラッパー オブジェクトが作成され、外部ストレージへのアクセスに使用されます。たとえば、スクリプトソースやウェブから受信したその他のコンテンツは、VM ヒープではなく外部ストレージに保存されます。
新しい JavaScript オブジェクトのメモリは、専用の JavaScript ヒープ(または VM ヒープ)から割り振られます。これらのオブジェクトは V8 のガベージ コレクタによって管理されるため、少なくとも 1 つの強参照がある限り存続します。
ネイティブ オブジェクトは、JavaScript ヒープ以外のすべてのオブジェクトです。ネイティブ オブジェクトは、ヒープ オブジェクトとは異なり、その存続期間全体で V8 ガベージ コレクタによって管理されず、JavaScript ラッパー オブジェクトを使用して JavaScript からのみアクセスできます。
Cons 文字列は、保存して結合された文字列のペアで構成されるオブジェクトであり、連結の結果です。cons 文字列の内容の結合は、必要に応じてのみ行われます。たとえば、結合された文字列のサブ文字列を作成する場合などです。
たとえば、a と b を連結すると、連結の結果を表す文字列(a, b)が得られます。後でその結果に d を連結すると、別の連結文字列((a、b)、d)が得られます。
配列 - 配列は、数値キーを持つオブジェクトです。これらは、大量のデータを保存するために V8 VM で広く使用されています。辞書のように使用される Key-Value ペアのセットは、配列によってバックアップされます。
一般的な JavaScript オブジェクトは、保存に使用される次の 2 つの配列型のいずれかです。
- 名前付きプロパティ、
- 数値要素
プロパティが非常に少ない場合、それらは JavaScript オブジェクト自体に内部的に格納できます。
Map - オブジェクトの種類とそのレイアウトを記述するオブジェクト。たとえば、マップは、プロパティへの高速アクセスのために暗黙的なオブジェクト階層を記述するために使用されます。
オブジェクト グループ
各ネイティブ オブジェクト グループは、相互参照を保持するオブジェクトで構成されています。たとえば、すべてのノードに親へのリンクと次の子と次の兄弟へのリンクがあり、接続されたグラフを形成する DOM サブツリーについて考えてみましょう。ネイティブ オブジェクトは JavaScript ヒープには表されません。そのため、サイズがゼロになります。代わりに、ラッパー オブジェクトが作成されます。
各ラッパー オブジェクトは、対応するネイティブ オブジェクトへの参照を保持し、コマンドをそのオブジェクトにリダイレクトします。オブジェクト グループは、ラッパー オブジェクトを保持します。ただし、GC はラッパーが参照されなくなったオブジェクト グループを解放できるため、回収不能なサイクルは発生しません。ただし、1 つのラッパーを解放し忘れると、グループ全体と関連するラッパーが保持されます。