メモリの用語

Meggin Kearney 氏
Meggin Kearney

このセクションでは、メモリ分析で使用される一般的な用語について説明します。この用語は、さまざまな言語のさまざまなメモリ プロファイリング ツールに適用できます。

ここで説明する用語や概念は、Chrome DevTools のヒープ プロファイラのことを指します。Java、.NET、その他のメモリ プロファイラを扱ったことがあれば、内容を簡単に復習できます。

オブジェクト サイズ

メモリは、プリミティブ型(数値や文字列など)とオブジェクト(連想配列)を持つグラフと考えてください。次のように、相互接続されたポイントの数を含むグラフとして視覚的に表すことができます。

メモリの視覚的表現

オブジェクトは、次の 2 つの方法でメモリを保持できます。

  • オブジェクト自体から直接取得。
  • 他のオブジェクトへの参照を保持して暗黙的に保持し、それらのオブジェクトがガベージ コレクタ(GC)によって自動的に破棄されないようにする。

DevTools のヒープ プロファイラ([プロファイル] の下に表示されるメモリの問題を調査するためのツール)を使用するとき、いくつかの異なる列の情報を見ることになるかもしれません。特に目を引くのは Shallow SizeRetained Size ですが、これらは何を表しているのでしょうか。

シャローで保持されたサイズ

シャローサイズ

これは、オブジェクト自体が保持するメモリのサイズです。

一般的な JavaScript オブジェクトには、説明用と即時値を格納するためのメモリが予約されています。通常、シャローサイズが大きいのは配列と文字列のみです。ただし、文字列と外部配列は多くの場合、レンダラのメモリ内にメイン ストレージがあるため、JavaScript ヒープには小さなラッパー オブジェクトのみが公開されます。

レンダラのメモリとは、検査対象ページがレンダリングされたプロセスのすべてのメモリです。つまり、ページのネイティブ メモリ + ページの JS ヒープメモリ + そのページによって開始されたすべての専用ワーカーの JS ヒープメモリです。ただし、小さなオブジェクトであっても、自動ガベージ コレクション プロセスによって他のオブジェクトが破棄されるのを防ぐことで、間接的に大量のメモリを保持できます。

保持サイズ

これは、オブジェクト自体と、GC ルートから到達不能になった依存オブジェクトが削除されると解放されるメモリのサイズです。

GC ルートは、ネイティブ コードから V8 以外の JavaScript オブジェクトを参照する場合に(ローカルまたはグローバルに)作成されるハンドルで構成されます。このようなハンドルはすべて、ヒープ スナップショット内の [GC ルート] > [ハンドルのスコープ] と [GC ルート] > [グローバル ハンドル] で確認できます。 このドキュメントでブラウザの実装の詳細に触れず、ハンドルについて説明すると、わかりにくいことがあります。GC ルートとハンドルはどちらも気にする必要はありません。

内部 GC ルートは数多くありますが、そのほとんどはユーザーにとって関心がありません。アプリケーションの観点からは、次の種類のルートがあります。

  • ウィンドウ グローバル オブジェクト(各 iframe 内)。ヒープ スナップショットには距離フィールドがあります。これは、ウィンドウからの最短保持パスにおけるプロパティ参照の数です。
  • ドキュメントを走査することで到達可能なすべてのネイティブ DOM ノードで構成されるドキュメントの DOM ツリー。それらすべてに JS ラッパーが含まれているわけではありませんが、ラッパーがあれば、ドキュメントが存続している間はラッパーも機能します。
  • オブジェクトがデバッガ コンテキストと DevTools コンソールによって保持されることがあります(コンソールの評価後など)。コンソールがクリアされ、デバッガにアクティブなブレークポイントがない状態で、ヒープ スナップショットを作成します。

メモリグラフはルート(ブラウザの window オブジェクトや Node.js モジュールの Global オブジェクト)から始まります。このルート オブジェクトの GC 方法は制御できません。

ルート オブジェクトを制御できない

ルートから到達できないものはすべて GC を取得します。

オブジェクト保持ツリー

ヒープは、相互接続されたオブジェクトのネットワークです。数学では、この構造はグラフまたはメモリグラフと呼ばれます。グラフは、エッジによって接続されたノードから構成されます。これらのノードの両方にラベルが付けられます。

  • ノード(またはオブジェクト)には、その作成に使用されたコンストラクタ関数の名前を使用してラベルが付けられます。
  • エッジのラベルはプロパティの名前を使用して付けられています。

ヒープ プロファイラを使用してプロファイルを記録する方法を学習する。以下のヒープ プロファイラの記録で注目すべき点として、距離(GC ルートからの距離)があります。同じタイプのほぼすべてのオブジェクトが同じ距離にあり、少数のオブジェクトの方が遠くにある場合は、調査する価値があります。

ルートからの距離

ドミネーター

各オブジェクトにはドミネーターが 1 つだけ存在するため、ドミネーター オブジェクトはツリー構造で構成されます。オブジェクトのドミネーターには、そのドミネーターが支配するオブジェクトへの直接参照がない場合があります。つまり、ドミネーターのツリーはグラフのスパニングツリーではありません。

下の図を参照してください。

  • ノード 1 がノード 2 を支配しています
  • ノード 2 がノード 3、4、6 を支配しています
  • ノード 3 がノード 5 を支配しています
  • ノード 5 がノード 8 を支配しています
  • ノード 6 がノード 7 を支配しています

ドミネーター ツリー構造

以下の例では、ノード #3#10 のドミネーターですが、#7 も GC から #10 へのすべての単純なパスに存在します。そのため、ルートからオブジェクト A までのすべての単純なパスにオブジェクト B が存在する場合、オブジェクト B はオブジェクト A のドミネーターになります。

ドミネーターのアニメイラスト

V8 の仕様

メモリをプロファイリングするときに、ヒープ スナップショットの表示形式が異なる理由を理解しておくと便利です。このセクションでは、特に V8 JavaScript 仮想マシン(V8 VM または VM)に対応するメモリ関連のトピックについて説明します。

JavaScript オブジェクト表現

プリミティブ型には次の 3 つがあります。

  • 数字(例:3.14159..)
  • ブール値(true または false)
  • 文字列(『Werner Heisenberg』)

他の値を参照することはできず、常にリーフまたは終端ノードになります。

数値は次のいずれかとして保存できます。

  • 小整数SMI)と呼ばれる 31 ビットのイミディエイト整数値。
  • ヒープ オブジェクト(ヒープ番号と呼ばれます)。ヒープ番号は、double 型など SMI 形式に収まらない値を格納する場合や、プロパティを設定する場合などに値をボックス化する必要がある場合に使用されます。

文字列は次のいずれかに格納できます。

  • VM ヒープ、または
  • レンダラのメモリの外部に配置します。ラッパー オブジェクトは、外部ストレージにアクセスするために使用されます。たとえば、スクリプト ソースやウェブから受信したコンテンツは、VM ヒープにコピーされるのではなく保存されます。

新しい JavaScript オブジェクト用のメモリは、専用の JavaScript ヒープ(または VM ヒープ)から割り当てられます。これらのオブジェクトは V8 のガベージ コレクタによって管理されるため、少なくとも 1 つの強力な参照がある限り存続します。

ネイティブ オブジェクトとは、JavaScript ヒープ内にない、その他すべてのオブジェクトのことです。ヒープ オブジェクトとは対照的に、ネイティブ オブジェクトは、存続期間を通じて V8 ガベージ コレクタによって管理されず、JavaScript ラッパー オブジェクトを使用して JavaScript からのみアクセスできます。

短所文字列は、格納されて結合された文字列のペアで構成されるオブジェクトで、連結の結果です。cons 文字列の内容の結合は、必要なときにだけ行われます。たとえば、結合された文字列の部分文字列を構築する必要がある場合などです。

たとえば、ab を連結すると、連結の結果を表す文字列(a, b)が返されます。その後、d をこの結果に連結すると、別の短所文字列((a, b, d))が返されます。

配列 - 配列は数値キーを持つオブジェクトです。これらは、大量のデータを保存するために V8 VM で広く使用されます。辞書のように使用される Key-Value ペアのセットは、配列によってバックアップされます。

一般的な JavaScript オブジェクトは、保存に使用される次の 2 つの配列型のいずれかです。

  • 名前付きプロパティ、
  • 数値要素

プロパティの数が非常に少ない場合は、JavaScript オブジェクト自体に内部的に格納できます。

Map - オブジェクトの種類とそのレイアウトを記述するオブジェクト。たとえば、マップを使用して暗黙的なオブジェクト階層を記述し、プロパティにすばやくアクセスできるようにします。

オブジェクト グループ

各ネイティブ オブジェクト グループは、相互参照を持つオブジェクトで構成されています。たとえば、DOM サブツリーで、すべてのノードに親へのリンクと、次の子および次の兄弟へのリンクが含まれていて、連結グラフが形成されているとします。ネイティブ オブジェクトは JavaScript ヒープでは表されないため、サイズはゼロです。代わりに、ラッパー オブジェクトが作成されます。

各ラッパー オブジェクトは、コマンドをリダイレクトするために、対応するネイティブ オブジェクトへの参照を保持します。次に、オブジェクト グループがラッパー オブジェクトを保持します。ただし、ラッパーが参照されなくなったオブジェクト グループを解放する GC では、収集不能なサイクルが生じることはありません。しかし、単一のラッパーをリリースするのを忘れると、グループ全体と関連するラッパーが保持されます。