https://morioh.com/a/ddf59961e87c/javascript-memory-leaks-causes-and-solutions
ガベージコレクションアルゴリズム
参照カウント
複数オブジェクトが相互参照している場合、GCはオブジェクトが使用されなくなった後でも収集しない。
function foo() {
var obj1 = {};
var obj2 = {};
obj1.x = obj2; // obj1 references obj2
obj2.x = obj1; // obj2 references obj1
return true;
}
foo();
マークアンドスイープ
そのオブジェクトに到達可能なことを示すフラグ(mark bit)を所有する。初期値は0。 マークフェーズでルートから到達可能なオブジェクトのマークビットが1にセットされる。 アルゴリズム実行中にプログラムの実行が一時停止される。
root(グローバル)
|-- A { marked: 1 }
|-- B { marked: 0, a:A }
|-- C { marked: 0 }
スイープフェーズでマークビットが0のオブジェクトはGCに収集される。
root(グローバル)
|-- A { marked: 1 }
|-- B { marked: 0, a:A }
不要な参照の作成
グローバル変数
グローバル変数はGCによって削除されない。 varは使用後にnullにしたり再割り当てすることが重要。 letでブロックスコープに変える。
クロージャ
var foo;
setInterval(() => {
foo = outer();
}, 1000);
function outer() {
var a = new Array(10000000);
function inner() {
if (a) { ... }
}
return () => {}
}
上の例では1秒おきにinnerの新しいインスタンスが生成される。 innerはどこからも呼び出されていないが、GCに収集されない。 innerを呼び出すとGCに収集される。
DOM参照
var p = document.createElement("p");
var ref = {
dom: document.querySelector("p");
};
document.body.removeChild(document.querySelector("p"));
上の例ではDOMを削除した後もrefの参照が残る。 DOMを格納する変数をローカル変数に変更すると、GCに収集される。
タイマー
var foo = {
timer: () => {
setTimeout(() => this.timer(), 10000);
};
};
foo.timer();
foo = null;
上の例では、元のオブジェクトの参照を失った後でもGCに収集されない。 setTimeout内で参照を提供する(コード例不明)。 新しいブラウザでは対策されている。
メモリリークの追跡
Chrome DevToolsのPerformanceタブで計測する。 JS Heepの上昇によってメモリ使用量が確認できる。
Chrome DevToolsのMemoryタブで確認する。 Heep snapshotを選択し計測を開始する。 start,endで2つスナップショットができる。 Comparisonを選択。DOM要素切り離しなど分析できる。
MemoryタブのAllocation instrumentation on timelineは定期的なスナップショットを生成する。