JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptの参照カウントガベージコレクションとは?仕組みと循環参照の限界を解説

参照カウント方式ガベージコレクションとは

参照カウント方式(Reference-counting)は、最もシンプルなガベージコレクション(GC)アルゴリズムです。このアルゴリズムは、「どこからも参照されなくなったオブジェクト」を常に監視しています。あるオブジェクトへの参照がひとつも残っていない状態になると、そのオブジェクトはメモリ上で不要な存在とみなされ、ガベージコレクションによって自動的に回収されます。

具体的には、オブジェクトが作成されたり他の変数やプロパティから参照されたりするたびに参照カウントが増加し、参照が解除されるたびに減少します。カウントがゼロになった時点で、そのオブジェクトは回収対象となるのです。

基本動作の例

var obj = {
    x: { y: 2 }
};
// ここで2つのオブジェクトが生成されました。
// 内側のオブジェクト { y: 2 } は、外側のオブジェクトの
// 「x」プロパティとして参照されています。
// したがって、どちらにも参照が残っているため、
// 回収されることはありません。

obj = 1; // 変数objに別の値を代入したことで、元の外側のオブジェクト
         // への参照は失われました。
         // さらに、内側の { y: 2 } も参照元を失い、
         // 参照カウントがゼロになったため、両方とも回収可能になります。

参照カウント方式の限界

一見理にかなっているこの方式ですが、循環参照(サイクル)が発生した場合に大きな弱点を抱えています。次の例で確認してみましょう。

循環参照の例

以下のコードでは、2つのオブジェクトが生成され、互いを参照し合うことで循環参照が生まれています。関数 f() の呼び出しが完了すると、obj1obj2 はスコープ外となり、外部からアクセスできない実質的に不要なオブジェクトになります。本来なら解放されてしかるべきですが、参照カウント方式では「それぞれのオブジェクトが相手から少なくとも1回は参照されている」と判断されるため、参照カウントは決してゼロにならず、どちらもガベージコレクションで回収されません。この結果、メモリリークが発生してしまいます。

function f() {
var obj1 = {};
var obj2 = {};
obj1.p = obj2; // o1 が o2 を参照
obj2.p = obj1; // o2 が o1 を参照。ここで循環参照が発生する。
}
f();

現代のJavaScriptエンジンでの対策

この循環参照の問題を回避するため、現在の主要なJavaScriptエンジン(ChromeのV8など)では、マーク・アンド・スイープ(Mark-and-Sweep)方式が採用されています。この方式では、ルート(グローバルオブジェクトなど)から到達可能なオブジェクトを順番に「マーク」していき、最終的にマークされなかった到達不能なオブジェクトだけを「スイープ(回収)」します。相互参照しているオブジェクトでも、外部から到達できなければ回収できるため、循環参照によるメモリリークを防ぐことができます。

  1. JavaScriptの「for...in」ステートメントとは?オブジェクトのプロパティをループ処理する方法を解説

    JavaScriptのfor...in文は、オブジェクトが持つすべてのプロパティ(列挙可能なプロパティ)を順番に取り出して処理するためのループ構文です。オブジェクト内の各キー(プロパティ名)にアクセスしながら、対応する値を取得したい場合に非常に便利です。for...inの基本的な構文for (let 変数名 in オブジェクト) { // 各プロパティに対して実行したい処理 }ループ変数には、各反復ごとにオブジェクトのプロパティ名(キー)が文字列として代入されます。値そのものを取得するには、「オブジェクト[変数名]」のようにブラケット記法を使ってアクセスします。サンプルコード以下は、for

  2. 【図解付き】JavaScriptにおけるオブジェクトの等価性をわかりやすく解説

    JavaScriptでは、文字列・数値・真偽値などのプリミティブ型はその「値」に基づいて比較されます。一方、オブジェクト(ネイティブオブジェクトでも独自に定義したカスタムオブジェクトでも)は「参照」に基づいて比較されます。 「参照による比較」とは、複数のオブジェクトがメモリ上の同じ場所を指しているかどうかを判定するという意味です。つまり、プロパティの中身が完全に一致していても、別々に生成された2つのオブジェクトは等しいとみなされません。 押さえておきたいポイント プリミティブ型: 値が同じであれば == や === の比較で true になる オブジェクト型: 同じインスタンス(同一の参照