2022年10月14日

ガベージコレクション

JavaScriptでのメモリ管理は、自動的かつ不可視の方法で行われます。プリミティブ、オブジェクト、関数を生成します...それらすべてにメモリを消費します。

何かが不要になった場合、何が起こりますか?JavaScriptエンジンはそれをどのように検出してクリーンアップしますか?

到達可能性

JavaScriptでのメモリ管理の主な概念は到達可能性です。

簡単に言えば、「到達可能な」値とは、何らかの方法でアクセス可能または使用可能な値です。メモリに確実に保存されます。

  1. 明らかな理由から削除できない、本質的に到達可能な値の基本セットがあります。

    たとえば

    • 現在実行中の関数、そのローカル変数とパラメータ。
    • ネストしたコールの現在のチェーン上のその他の関数、ローカル変数とパラメータ。
    • グローバル変数。
    • (他の内部のものもあります)

    これらの値はルートと呼ばれます。

  2. 他の値はすべて、ルートから参照または参照チェーンによって到達可能な場合、到達可能と見なされます。

    たとえば、グローバル変数内にオブジェクトがあり、そのオブジェクトには別のオブジェクトを参照するプロパティがある場合、そのオブジェクトは到達可能とみなされます。そして参照されているオブジェクトも到達可能です。詳細な例は次のとおりです。

ガベージコレクターと呼ばれる、JavaScriptエンジン内のバックグラウンドプロセスがあります。詳細。すべてのオブジェクトを監視し、到達不能になったオブジェクトを削除します。

簡単な例

最も単純な例を次に示します

// user has a reference to the object
let user = {
  name: "John"
};

ここでは、矢印はオブジェクト参照を表しています。グローバル変数"user" はオブジェクト{name: "John"}(簡潔にジョンと呼びましょう)を参照しています。ジョンの"name" プロパティはプリミティブを格納しているため、オブジェクト内に描かれています。

user の値が上書きされると、参照は失われます。

user = null;

これでジョンにはアクセスできなくなります。アクセスする方法も、参照もありません。ガベージコレクターはデータをジャンクしてメモリを解放します。

2 つの参照

user からadmin に参照をコピーしたところを想像してみましょう。

// user has a reference to the object
let user = {
  name: "John"
};

let admin = user;

ここで同じことを行うとします。

user = null;

…このオブジェクトにはグローバル変数admin を介してアクセスできるため、メモリに保持する必要があります。admin も上書きすると削除できます。

相互連結されたオブジェクト

ここでは、より複雑な家族の例を説明します。

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});

関数marry は、2 つのオブジェクト同士に相互参照を与えて「結婚」させ、その両方が含まれる新しいオブジェクトを返します。

結果のメモリ構造

現在のところ、すべてのオブジェクトにアクセスできます。

ここで、2 つの参照を削除してみましょう。

delete family.father;
delete family.mother.husband;

これらの 2 つの参照のうち 1 つだけを削除しても、すべてのオブジェクトにはアクセスできるため、不十分です。

ただし、両方とも削除すると、ジョンには着信参照がなくなっていることがわかります。

送信参照は関係ありません。着信参照のみがオブジェクトにアクセス可能になります。そのため、ジョンにはアクセスできなくなり、アクセスできなくなったすべてのデータと共にメモリから削除されます。

ガベージコレクションの後

到達不能な島

相互連結されたオブジェクトの島全体にアクセスできなくなり、メモリから削除される可能性があります。

ソースオブジェクトは上記と同じです。その後

family = null;

インメモリの画像は次のようになります。

この例は、到達可能性の概念がいかに重要であるかを示しています。

ジョンとアンがまだリンクされており、どちらも着信参照を持っていることは明らかです。しかし、それだけではありません。

以前の"family" オブジェクトがルートからリンク解除され、参照されなくなりました。そのため、島全体にアクセスできなくなり、削除されます。

内部アルゴリズム

基本的なガベージコレクションアルゴリズムは「マークアンドスイープ」と呼ばれます。

次の「ガベージコレクション」ステップが定期的に実行されます。

  • ガベージコレクターはルートを取得し、「マーク」します(記憶)。
  • 次に、それらからすべての参照を参照して「マーク」します。
  • 次に、マークされたオブジェクトを参照して、それらの参照をマークします。アクセスしたすべてのオブジェクトがメモリーされるため、同じオブジェクトが将来 2 回以上アクセスされることはありません。
  • …そして、(ルートからの) 到達可能なすべての参照がアクセスされるまで続けます。
  • マークされたオブジェクト以外のすべてのオブジェクトは削除されます。

たとえば、次のようなオブジェクト構造があるとします。

右側に「到達できない島」があるのをはっきりと確認できます。さて、「マークアンドスイープ」ガベージコレクターがどのように対処するか見てみましょう。

最初のステップでルートがマークされます。

次に、それらの参照をたどり、参照されているオブジェクトをマークします。

…そして、可能な限りさらに参照をたどります。

ここで、このプロセスで参照できなかったオブジェクトは到達不能と見なされ、削除されます。

このプロセスは、すべての参照に通じる巨大なバケツの塗料をルートからこぼして、到達可能なすべてのオブジェクトをマークし、マークされていないオブジェクトは削除されるプロセスと見なすこともできます。

これがガベージコレクションの仕組みの概念です。JavaScript エンジンは多くの最適化を適用して実行速度を向上させ、コードの実行に遅延を発生させません。

最適化の一部

  • 世代別コレクション - オブジェクトは2 つのセット、「新しいもの」と「古いもの」に分割されます。一般的なコードでは、多数のオブジェクトが短い寿命を持ちます。これらは出現し、その役割を果たし、すぐに消失するため、新しいオブジェクトを追跡し、該当する場合にメモリからクリアする意味があります。十分な時間生き残ったものは「古い」ものとなり、あまり調べられなくなります。
  • 増分コレクション - オブジェクトが多数あり、一度にオブジェクト セット全体に歩いてマークを付けようとすると、時間がかかり、実行時に目に見える遅延が発生する可能性があります。そのため、エンジンは既存オブジェクトのセット全体を複数の部分に分割します。そして、これらの部分をそれぞれ順番にクリアします。一度にすべてを処理するのではなく、小さく多数のガベージ コレクションが行われます。これにはそれらの間で変更を追跡するための追加の記録が必要になりますが、大きな遅延ではなく、小さな遅延が多数発生します。
  • アイドル時間コレクション - ガベージ コレクターは、できるだけ CPU がアイドル状態にあるときに実行するよう試み、実行への影響を最小限に抑えます。

ガベージ コレクション アルゴリズムには、他にも最適化とバリエーションがあります。ここでは説明したいと思っていますが、さまざまなエンジンでさまざまな微調整と手法が実装されているため、控える必要があります。さらに重要なことは、エンジンが開発さえるにつれて状況が変わるため、実際のニーズなしに「事前に」深く勉強しても価値がないと思われます。もちろん、純粋な興味の問題である場合を除きます。この場合は、以下にいくつかのリンクがあります。

要約

知っておくべき主な事項

  • ガベージ コレクションは自動的に実行されます。それを強制したり防いだりはできません。
  • オブジェクトは到達可能な間、メモリに保持されます。
  • 参照されることは、(ルートから) 到達可能であることと同じではありません。相互リンクされたオブジェクトのパックは、上の例で見たように、全体として到達不能になる可能性があります。

最新のエンジンは、高度なガベージ コレクション アルゴリズムを実装しています。

一般的な書籍「ガベージ コレクション ハンドブック: 自動メモリ管理の技術」(R. ジョーンズ他) では、そのいくつかが取り上げられています。

低レベル プログラミングに詳しい場合は、V8 のガベージ コレクターに関する詳細な情報は記事 V8 のツアー: ガベージ コレクション にあります。

V8 ブログ でも、メモリ管理の変更に関する記事が適宜公開されています。当然のことながら、ガベージ コレクションの詳細を学ぶためには、V8 の内部構造全般について学び、V8 エンジニアの 1 人として働いた Vyacheslav Egorov のブログを読むことで準備した方がよいでしょう。私が「V8」と言うのは、インターネット上の記事で最もよく取り上げられているためです。他のエンジンでは、多くのアプローチが似ていますが、ガベージ コレクションは多くの点で異なります。

低レベルの最適化が必要な場合は、エンジンの詳細な知識は役立ちます。言語に精通した後の次のステップとして計画を立てるのが賢明でしょう。

チュートリアルマップ

コメント

コメントを投稿する前にこれを読んでください...
  • 改善案がある場合は、コメントではなく GitHub の課題を送信 するか、プル リクエストを送信してください。
  • 記事で理解できない部分があれば - 説明をお願いします。
  • コードのいくつかを挿入するには、<code> タグを使用し、複数の行にまたがる場合には <pre> タグでラップし、10 行を超える場合は sandbox (plnkrjsbincodepen…) を使用してください。