ガベージコレクションは、JavaScriptのメモリ管理の仕組みで、使用されなくなったオブジェクトを削除し、メモリを解放する。
実行は、ガベージコレクタと呼ばれる、バックグラウンドプロセスにより自動的に実行される。ガベージコレクタはオブジェクトを監視し、到達不可能になったオブジェクトを削除する。
C++とかだと、デストラクタやスマートポインタなどをプログラマが用いることで実現する管理を、JSではガベージコレクタが自動実行してくれる。素晴らしい🧐
到達性
JavaScriptのメモリ管理の主要コンセプトは到達性で、『到達可能な』値とは、何らかの形でアクセス可能、もしくは使用可能な値のことを指す。それらの値はガベージコレクタにより削除されず、メモリに格納されることが保証される。
具体的な例をいくつか示す。
- 本質的に到達可能な値の基本セット(ルートと呼ばれる)
- 実行中の関数のローカル変数とパラメータ
- ネストされた呼び出しの関連する他関数のローカル変数とパラメータ
- グローバル変数
上記以外は、参照または参照チェーンにより、ルートから到達可能できるもの
例えば、ローカル変数にオブジェクトAがあり、オブジェクトAがオブジェクトBへの参照を持っている場合、オブジェクトBは到達可能とみなされる。
到達性の例
シンプルなパターン
例えば、次のようなオブジェクトがあるとする。
let user = {
name: "John"
};
参照を図示すると次のような感じ。
ここで、user
の値を上書きすると、オブジェクトへの参照がなくなり到達不可能になり、ガベージコレクタによりオブジェクトは破棄される。
user = null;
2つの参照がある場合
例えば、次のようなオブジェクトがあるとする。
let user = {
name: "John"
};
let admin = user;
シンプルな例と同様に、user
を上書きしても、オブジェクトへの参照は admin
からのものが残っているためオブジェクトは削除されない。admin
の値を上書きするとオブジェクトは削除される。
連結されたオブジェクト
次のようなオブジェクトを考える。
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
上記の通り、すべてのオブジェクトはルート(<global variale>
)から到達可能になっている。
2つの参照を削除してみる。
delete family.father;
delete family.mother.husband;
name: "Ann"
のオブジェクトは到達可能だが、name: "John"
のオブジェクトはルートから到達できるなくなるため、ガベージコレクタにより削除される。
到達不可能な島
連携されたオブジェクトの全体が到達不可能になり、メモリから削除される場合もある。例えば、上の
family
で示した例において、family
の参照を削除してみる。
family = null;
family
から参照が消えたことで、ルートから参照できなくなり、島全体が到達不可能になったため削除されることになる。
ガベージのアルゴリズム
ガベージは、『マークアンドスイープ』と呼ばれる方法で行われる。具体的には次の手順が定期的に実行される。
- ガベージコレクタはルートを取得し、 それらを “マーク” (記憶)する
- 次に、そこからのすべての参照先を訪れ、マークする
- 次に、マークされたオブジェクトへアクセスし、それらの参照をマークする
- 上記を繰り返し、すべての到達可能な(ルートからの)参照が訪問されるまで続ける
- マークされたオブジェクトを除いた、すべてのオブジェクトが削除されます。
例えば、次のようなオブジェクトを考える。
右側のオブジェクトは、明らかに島全体が到達不可能な島になっている。ガベージコレクタがどう処理するかを追う。
ルートをマークする
ルートの参照をマークする
それらの参照も辿れるところまでマークする
マークされていないオブジェクトを到達不可能と見做し、削除する