この記事は編集レビューを必要としています。ぜひご協力ください。
この翻訳は不完全です。英語から この記事を翻訳 してください。
導入
Cのような低レベル言語は、malloc()
やfree()
のような底レベルメモリ管理プリミティブを持ちます。他方では、JavaScriptの値は、もの(オブジェクト、文字列など)が生成されるときに割り当てられます。そして、"自動的に"使用されないとき開放されます。後者のプロセスがガーベジコレクションを呼び出します。この"自動的"は混乱の源であり、JavaScript (そして、高レベル言語) 開発者に彼らは、メモリ管理を気にしないことを決定することができる印象を与えます。これは誤りです。
メモリライフサイクル
プログラミング言語に関係なく、メモリのライフサイクルはかなり常に同じです:
- あなたが必要とするメモリを割り当てます
- 使用します (読み, 書き)
- それはもはや必要ないときに割り当てられたメモリを解放します
1と2に関してはすべての言語で明示的に行われます。最後の3は、低レベルの言語では明示的ですが、JavaScriptのような高水準言語では、ほとんど暗黙的に行われます。
JavaScriptでの割り当て
値の初期化
割り当てでプログラマを悩まさないために、JavaScriptでは値を宣言したときと同時にメモリの割り当ても行われます。
var n = 123; // 数値を格納するメモリが割り当てられます var s = "azerty"; // 文字列を格納するメモリが割り当てられます var o = { a: 1, b: null }; // オブジェクトとそれに含まれる値を格納するためのメモリが割り当てられます // (オブジェクトの例と同じように) 配列とそれに含まれる値を格納するための // メモリが割り当てられます var a = [1, null, "abra"]; function f(a){ return a + 2; } // 関数を格納するメモリが割り当てられます (関数は呼び出し可能なオブジェクトです) // 関数式でもメモリの割り当てが行われます someElement.addEventListener('click', function(){ someElement.style.backgroundColor = 'blue'; }, false);
関数呼び出しを介して割り当て
一部の関数呼び出しでは、オブジェクトの割り当てが発生します。
var d = new Date(); // Dateオブジェクトの割り当て var e = document.createElement('div'); // DOM要素の割り当て
いくつかのメソッドは、新しい値またはオブジェクトを割り当てます:
var s = "azerty"; var s2 = s.substr(0, 3); // s2は新しい文字列 // JavaScriptでは文字列はイミュータブルな値なので、 // もしかすると実際にはメモリ割り当てが行われず、 // [0, 3]という文字の範囲だけを割り当てるかもしれません。 var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // a, a2の内容を繋ぎ合わせた4要素の配列が作成されました
値の使用
値を使用することは、基本的に割り当てられたメモリで読み書きすることを意味します。これは変数やオブジェクトの値を読み書きすることや引数を関数に渡すことによって行われます。
メモリが不要になったときに解放
メモリ管理の問題のほとんどは、この段階に来ます。ここで最も難しい作業は、「割り当てられたメモリは、もはや必要とされていない」ときを発見することです。プログラム内のどこで、そのようなメモリの断片が不要になって解放する必要があるかを決定するには、開発者による判断が必要なことが多いです。
高レベル言語には、仕事がメモリ割当てを追跡することである「ガベージコレクタ」と呼ばれるソフトウェアを埋め込み、割り当てられたメモリの一部がもはや、その場合に必要とされていないときに見つけるために使用し、それが自動的に解放されます。メモリの一部のピースが必要とされているかどうかを知ることの一般的な問題は決定不能であるため、このプロセスは近似的なものです(アルゴリズムによって解決できません)。
ガーベジコレクション
自動的にいくつかのメモリは"もはや必要ない"かどうかを見つけるという一般的な問題は、上述したように決定不能です。その結果、ガベージコレクションは、一般的な問題の解決策に制限を課しています。このセクションでは、メインのガベージコレクションのアルゴリズムとその限界を理解するために必要な概念を説明します。
リファレンス
ガーベジコレクションアルゴリズムが依存している主な概念は、リファレンス (reference)の概念です。メモリ管理のコンテキスト内で、オブジェクトは別のオブジェクトを参照するように、前者は後者へのアクセス権を(暗黙的または明示的)に持っている場合、言われています。例えば、JavaScript オブジェクトはprototypeとプロパティ値への参照を持ちます。(明示的な参照)
これに関連して、"オブジェクト"の概念は通常のJavaScriptオブジェクトよりも広いものに拡張され、また、関数のスコープが含まれています(または、グローバルレキシカルスコープ)。
参照カウントのガベージコレクション
これは、最も素朴なガベージコレクションアルゴリズムです。このアルゴリズムは、"オブジェクトがそれに参照する他のオブジェクトを持っていない"場合に"オブジェクトはもう必要ない”として扱います。オブジェクトに対する参照の数がゼロ個になった場合、オブジェクトはガベージコレクションの対象として処理されます。
例
var o = { a: { b:2 } }; // 2個のオブジェクトが作成されました。一方はもう一方のプロパティとして参照されています。 // もう一方は変数 'o' に代入されているため、こちらも同じく参照されています。 // 明らかに、どちらのオブジェクトもガベージコレクションの対象になりません。 var o2 = o; // 'o2' がオブジェクトを参照するようになったため、 // 参照カウントは2になりました o = 1; // 'o' はもうオブジェクトを参照していません。これで、このオブジェクトを // 参照するのは 'o2' だけになりました var oa = o2.a; // プロパティ 'a' を参照しています。 // 参照先のオブジェクトは、この時点で2つの箇所から参照されています。 // ひとつはプロパティの値として、もうひとつは変数 'oa' の値としてです。 o2 = "yo"; // もともと 'o' に代入されていたオブジェクトを参照するものはいなくなりました。 // このため、このオブジェクトはガベージコレクトの対象となります。 // しかしながら、そのオブジェクトのプロパティ 'a' が指していたオブジェクトは // まだ変数 'oa' に参照されているため、解放することはできません。 oa = null; // もともと 'o' に代入されていたオブジェクトのプロパティ 'a' が指していた // オブジェクトへの参照が一つも無くなったため、ガベージコレクションの対象 // となりました。
制限: サイクル
それがサイクルになるとき制限があります。次の例では、2つのオブジェクトが作成され、相互に参照されています。 – このようにして、サイクルを生成します。これらは、関数呼び出しの後に関数スコープの外から取得することはできません。だから、効果的に役に立たなく、本来は開放することができるものです。しかし、参照カウントアルゴリズムは、両方のオブジェクトのそれぞれが少なくとも一回参照されているので、どれもガベージコレクトすることができないことを考えています。
function f(){ var o = {}; var o2 = {}; o.a = o2; // o references o2 o2.a = o; // o2 references o return "azerty"; } f();
Real-life example
Internet Explorerの6および7は、DOMオブジェクトに対しては参照カウント式のガベージコレクタを使用していることが知られています。サイクルは、メモリリークを発生させることができるよくある間違いです:
var div; window.onload = function(){ div = document.getElementById("myDivElement"); div.circularReference = div; div.lotsOfData = new Array(10000).join("*"); };
上記の例では、DOM要素"myDivElement"は"circularReference"プロパティに自身への循環参照を持っています。そのプロパティが明示的に削除またはヌルにされていない場合, 参照カウントのガベージコレクタは、常に少なくとも1つの参照そのまま有することになり、それがDOMツリーから削除された場合でも、メモリ内のDOM要素を維持します。DOM要素は、大量のデータを保持している場合("lotsOfData"プロパティで上記の例に示されています)、このデータによって消費されるメモリが解放されることはありません。
マーク·アンド·スイープアルゴリズム
このアルゴリズムは、"オブジェクトが到達不能である"場合に"オブジェクトはもう必要ない"として扱います。
このアルゴリズムは、rootと呼ばれるオブジェクトのセットについての知識を前提としています(JavaScriptでは、rootはグローバルオブジェクトです)。定期的に、ガベージコレクタは、これらのrootから開始し、これらのrootから参照されるすべてのオブジェクト、それから、これらの中から参照されるすべてのオブジェクトなどを見つけます。rootから開始すると、ガベージコレクタは、すべての到達可能オブジェクトを見つけ、すべての非到達可能オブジェクトをガベージコレクトします。
"オブジェクトへの参照がゼロ個"であるときこのオブジェクトが到達不能であることになるので、このアルゴリズムは、前のものよりも優れています。サイクルで見てきたように逆は真ではありません。
2012年の時点で、すべての近代的なブラウザでは、マークアンドスイープ式のガベージコレクタが含まれています。過去数年間でJavaScriptのガベージコレクション(世代別/インクリメンタル/並行/並列ガベージコレクション)の分野で行われたすべての改善は、このアルゴリズムの実装の改善であって、ガベージコレクションアルゴリズム自体に対する改善でも、"もうオブジェクトが必要とされていない"と扱う基準を変えるものでもありません。
サイクルはもはや問題ではありません
最初の上記の例では、関数呼び出しが戻った後、2つのオブジェクトは、グローバルオブジェクトから到達可能な何かによってもはや参照されません。その結果、それらは、ガベージコレクタによって到達不能として検出されます。
同じことは、第二の例でも言えます。divとそのハンドラがrootから到達不能になったら、それらは両方ともお互いを参照するにもかかわらずガベージコレクトことができます。
制限: 明示的に到達不能にされる必要があるオブジェクト
これは制限としてマークされていますが、誰もが通常のガベージコレクションについてそれほど気にしない理由があるのは、実際にはめったに達していないものです。