2023-11-22-07-38

Recent posts

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は定期的なスナップショットを生成する。