[JVM]-[Java 仮想マシンの徹底理解学習メモ]-第 3 章-ガベージ コレクターとメモリ割り当て戦略

オブジェクトが生きているかどうかを判断する

参照カウントアルゴリズム

オブジェクトに参照カウンタを追加します。オブジェクトへの参照があるたびに、カウンタ値は 1 ずつ増加します。参照が期限切れになると、カウンタ値は 1 ずつ減少します。カウンタが常に 0 のオブジェクトは使用できません。参照カウント アルゴリズムでは、オブジェクト間の循環参照、つまり 2 つのオブジェクトが相互に参照する問題を解決するのは困難ですが、これら 2 つのオブジェクトは他の場所では使用されなくなります。この時点で、2 つのオブジェクトは次のようになります
リサイクルされますが、整理番号が0ではないためリサイクルできません。また、オブジェクトごとにカウンタを設定する必要があるため、オブジェクトの数が多いとカウンタの数も多くなり、占有スペースも非常に大きくなる。

到達可能性分析アルゴリズム

GC ルートと呼ばれる一連のルート オブジェクトを開始ノード セットとして使用します。これらのノードから開始して、参照関係に従って下方向に検索します。検索プロセスによって移動するパスは参照チェーンと呼ばれます。オブジェクト間に参照がない場合、と GC ルート チェーンが接続されているか、オブジェクトが GC ルートから到達できない場合、そのオブジェクトは使用できなくなったことを意味します。

実際にオブジェクトをデッドと宣言するには、少なくとも2 つのマーキングプロセスが必要です: 到達可能性分析後に GC ルートに接続されている参照チェーンがオブジェクトにないことが判明した場合、そのオブジェクトは初めてマークされます。その後、スクリーニングが実行され、スクリーニング条件が設定されます。オブジェクトは、finalize() メソッドを実行する必要がありますか? オブジェクトがFinalize() メソッドをオーバーライドしていない場合、またはその Finalize() メソッドが仮想マシンによって呼び出された場合、仮想マシンは実行する必要がないとみなします。そして二度目にマークします

したがって、オブジェクトはそれ自体を保存できます。まず、finalize() メソッドをオーバーライドし、実行中にそれ自体 (つまり this) を参照チェーン上の任意のオブジェクトに割り当てます。Finalize() メソッドは最大 1 回しか実行されないため、このセルフレスキューも 1 回しか実行できませんが、finalize() メソッドの使用は推奨されません。

GC ルートとして利用できる固定オブジェクト

  1. 各スレッドが呼び出すメソッドスタックで使用されるパラメータ、ローカル変数、一時変数など、仮想マシンスタック(スタックフレーム内のローカル変数テーブル)内で参照されるオブジェクト
  2. ネイティブ メソッド スタック JNI で参照されるオブジェクト (いわゆるネイティブ ローカル メソッド)
  3. Javaクラスの参照型静的変数など、メソッド領域のクラス静的プロパティによって参照されるオブジェクト
  4. 文字列定数プールの参照など、メソッド領域の定数によって参照されるオブジェクト String Table
  5. 同期ロック (synchronized キーワード) によって保持されているすべてのオブジェクト
  6. Java仮想マシンの内部状況を反映するJMXBean、JVMTIに登録されるコールバック、ローカルコードキャッシュなど。

GC Roots のオブジェクトとして使用されている必要があるため、メソッド スタック内で参照されるオブジェクトを考えることができますが、メソッド スタックには当然ながら通常のメソッドとローカル メソッド、グローバルに利用可能なクラスの静的変数と定数が含まれます。ロックに保持されているオブジェクトは使用されるだけでなく、自由に死ぬこともできません。

引用について

JDK 1.2 以降、参照の概念は拡張され、次の 4 つのタイプに分割されました。

  1. 強参照 強参照強参照強力参照: 「参照」の伝統定義は、コード内のユビキタス参照割り当て、つまり、「Object obj = new Object()」のような参照関係です強い参照関係があるため、
    それがまだ存在する場合、コレクターはそれを再利用することはありません。
  2. ソフトリファレンス ソフトリファレンスソフト参照: ソフト参照にのみ関連付けられているオブジェクト。システムでメモリ オーバーフロー例外が発生する前に、これらのオブジェクトは 2 回目のリサイクルのリサイクル スコープに含まれます。このリサイクルがまだ行われない場合ただし、メモリが十分にあるとメモリ オーバーフロー例外がスローされます。
  3. 弱参照 弱い参照弱い参照We ak Reference :強度はソフト参照より弱いため、弱い参照に関連付けられたオブジェクトは、次のガベージ コレクションが発生するまでの存続できますガベージ コレクターが動作を開始すると、現在のメモリが十分であるかどうかに関係なく、弱い参照にのみ関連付けられているオブジェクトがリサイクルされます。
  4. ファントムリファレンス ファントムリファレンスファントム参照: 「ゴースト参照」または「ファントム参照」とも呼ばれます。オブジェクトに仮想参照があるかどうかは、その生存時間にまったく影響せず、オブジェクト取得すること不可能です仮想参照、オブジェクト インスタンス。オブジェクトへのファントム参照を設定する唯一の目的は、オブジェクトがリサイクルされたときにシステム通知を受け取ることです。

JDK 1.2 以降では、ソフト参照、弱参照、仮想参照を実装するために、SoftReference クラス、WeakReference クラス、および PhantomReference クラスが提供されています。

ガベージ コレクションを追跡するガベージ コレクション アルゴリズム

リサイクル対象物がどのようなものであるかがわかったら、次はどのようにリサイクルするかを検討します。

オブジェクトが生きているかどうかを判断するアルゴリズムに従って、ガベージ コレクション アルゴリズムは、参照カウント ガベージ コレクション追跡ガベージ コレクションに分類できます到達可能性分析アルゴリズムは JVM で使用されるため、ここでは追跡ガベージ コレクション アルゴリズムについてのみ説明します。

世代別コレクション理論

ガベージ コレクターの設計原則の多くは、世代別コレクション理論に基づいています。

  1. 弱い生成仮説: ほとんどの物体は生まれて死ぬ
  2. 強力な生成仮説: ガベージ コレクション プロセスで生き残る回数が増えるほど、死ぬのが難しくなります。
  3. 世代間引用仮説: 世代間引用は、同世代での引用に比べて非常に少数にすぎません。

1 番目と 2 番目の仮説からわかることは、コレクターはJava ヒープをさまざまな領域に分割しリサイクルされたオブジェクトをその年齢に応じて保管するためにさまざまな領域に割り当て、さまざまな生存特性に基づいてさまざまな収集戦略を採用する必要があるということです。

さまざまな生死の特性に従って、少なくとも Java ヒープは、新世代旧世代の2 つの領域に分けることができます。新世代は「生きて消える」オブジェクトに対応し、旧世代はオブジェクトに対応します。収集は、マイナー GC (新しい世代の収集)、メジャー GC (古い世代の収集)、フル GC (Java ヒープ全体の収集) など
の領域に応じて実行されます。マーククリアアルゴリズム、マークコピーアルゴリズムマーク照合アルゴリズムなど、領域ごとに異なるアルゴリズムが採用されています。

全体として、それはすべて世代別コレクション理論に由来しています。

世代別コレクションの明らかな問題は、オブジェクト間の世代間参照の問題です。新しい世代のオブジェクトが古い世代によって参照される可能性は十分にあります。この領域で生き残っているオブジェクトを見つけるには、次を使用する必要があります。 GC ルートが修正されました。また、到達可能性分析アルゴリズムの正確性を保証するために、古い世代全体のすべてのオブジェクトが追加でトラバースされます。古い世代全体のオブジェクトをトラバースすることは、間違いなくパフォーマンスに大きな負荷がかかります。

したがって、上記の 3 番目の経験則が導出されます。これは実際には最初の 2 つの推論から導出されています。つまり、相互に参照する 2 つのオブジェクトは同時に生きるか死ぬ傾向があるはずです。たとえば、新世代オブジェクトへの世代間参照がある場合、旧世代オブジェクトは消滅しにくいため、新世代オブジェクトは存続し、代替参照も排除されます。

したがって、少数の世代間参照のために古い世代全体をたどる必要はありません。新世代でグローバル データ構造を構築するだけですメモリーセット(セットを記憶)、この構造は古い世代をいくつかの小さなブロックに分割し、どのブロックが世代間参照を持つかを識別します。その後マイナー GC を実行する場合は、メモリ上に世代間参照を持つ旧世代領域のオブジェクトを GC ルートに追加するだけで済みます。

メモリ セットを使用する方法では、オブジェクトの参照関係が変化したときに記録されたデータを維持する必要がありますが、旧世代のオブジェクト全体を走査するコストと比較すると、依然としてコスト効率が高くなります。

ガベージコレクションアルゴリズム

マークスイープアルゴリズム

アルゴリズム: まず、リサイクルする必要があるすべてのオブジェクトにマークを付け、マークが完了すると、マークされたオブジェクトは均一にリサイクルされます。逆に、残っているすべてのオブジェクトにマークを付け、マークされていないすべてのオブジェクトをリサイクルすることもできます。

短所:

  • 実行効率が不安定: Java ヒープに多数のオブジェクトが含まれており、それらのほとんどをリサイクルする必要がある場合、より多くのマーキングおよびクリア アクションが必要となり、マーキングおよびクリア プロセスの実行効率がオブジェクトによって異なります。 . 数量が増えると減少します
  • メモリ空間の断片化: マークのクリア後、多数の不連続なメモリ フラグメントが生成されます。空間フラグメントが多すぎると、将来より大きなオブジェクトを割り当てる必要があるときに連続メモリ スペースが不十分になり、ガベージ コレクションをトリガーする必要がある可能性があります。あらかじめ。
    マークスイープアルゴリズム

マークコピーアルゴリズム

半分の領域の重複

多数のリサイクル可能なオブジェクトに直面したときのマークアンドスイープアルゴリズムの実行効率が不安定であるという問題を解決するためにこの論文は提案しました半分の領域の重複アルゴリズム: Java ヒープの使用可能なメモリを同じサイズの 2 つのブロックに分割し、一度に 1 つのブロックのみを使用します。使用されたブロックのメモリが使い果たされたら、残りのオブジェクトを別のブロックにコピーし、残りのオブジェクトを別のブロックに移動します。使用済みメモリ領域
一度にクリアすることは、領域全体がいっぱいになるまで待つのではなく、領域の半分がいっぱいになったときにメモリ領域をクリアすることと同じです。このようにして、毎回リサイクルする必要があるオブジェクトの数が減ります。実行効率の問題もある程度緩和されています。

メモリ内のほとんどのオブジェクトが存続する場合、大量のメモリ間コピー オーバーヘッドが生成されます。また、メモリ内のほとんどのオブジェクトがリサイクルされる場合、生き残ったオブジェクトのコピーに必要なオーバーヘッドは非常に小さくなります。そのため、アルゴリズムは次のようになります。半分の領域に残っているオブジェクトを他の半分の領域にコピーすると、スペースの断片化の問題を引き起こすことなく、規則正しく整然と配置できます。

短所: 使用可能なメモリが元のサイズの半分に減り、多くのスペースが無駄になります。
マークコピーアルゴリズム

アピールスタイルリサイクル

Appel スタイルのリサイクルは、より最適化されたハーフパーティション レプリケーション生成戦略です。これについては後で説明します。シリアルパーニュー新世代のコレクターは皆、この戦略を採用しています。

具体的な方法: 新しい世代をより大きな Eden スペースと 2 つの小さな Survivor スペース (それぞれfrom Survivor と To Survivor ) に分割します。メモリを割り当てるには、毎回 Eden と From Survivor のみを使用します。ガベージ コレクションが発生した場合は、Eden が使用されます。 From Survivor から別の To Survivor スペースにある生存オブジェクトを一度に削除し、Eden スペースと From Survivor スペースを直接クリーンアップします。
HotSpot 仮想マシンのデフォルトの Eden と Survivor のサイズ比は 8:1 です。つまり、各新しい世代で利用可能なメモリ領域は、新しい世代の容量全体の 90% であり、これにより、ハーフエリア コピーの過度の無駄の問題が解決されます。空間。

新しい世代で生き残るオブジェクトはほとんどありませんが、各コレクションでオブジェクトの 10% 以下が生き残ることを 100% 保証する方法は誰にもありません。生き残るオブジェクトが 10% を超えると、残りの To Survivor は維持されなくなります。これらのオブジェクトには、この問題を解決するための保証メカニズムを割り当てる必要があります。最後のマイナー GC で生き残ったオブジェクトを保存するのに十分なスペースが To Survivor にない場合、収容できないオブジェクトは直接 To Survivor に入力されます
古い世代。

ほとんどの場合、オブジェクトは新しい世代の Eden に割り当てられますが、Eden 領域に十分な割り当て領域がない場合、仮想マシンはマイナー GC を開始します。

なんで生存者が2人もいるの?

2 つの Survivor でメモリの断片化の問題を解決します。Survivor
が 1 つだけ存在し、Eden に残っているすべてのオブジェクトがこの Survivor に配置されるとします。次のマークでは、エデンに残っているオブジェクトは保存のために Survivor にきちんとコピーできますが、もともと Survivor に保存されていたオブジェクトの一部もリサイクルされ、一部は生き残る可能性がありますが、これらの部分をコピーして保存する場所はありませ。生き残ったオブジェクトの場合、他に生き残るものがないため、メモリの断片化の問題が発生します。
言い換えれば、ある領域にオブジェクトが格納されている限り、その領域のメモリの断片化は避けられません。解決策は、常に空であり、オブジェクトは格納されません。

したがって、From Survivor と To Survivor の 2 つの Survivor が必要です。オブジェクトが保存されるたびに、オブジェクトは Eden と From に保存されます。ガベージが収集されると、Eden と From に残っているオブジェクトは保存のために To にきちんとコピーされます。 Eden の場合 From ですべてをリサイクルし、To と ID を交換します

これにより、 To Survivor が常に空になり、Eden と From の生き残ったオブジェクトのコピーと整理に使用できるようになり、空間の断片化の問題が解決されます。

そして、なぜもっと生存者を増やさないのでしょうか? 空間が分割されればされるほど、各生存者の空間は小さくなり、空間を埋めることが容易になり、毎回生き残ることができるオブジェクトの数を予測することは不可能になります。小さすぎるため、サバイバーを保管できません。そして、問題を解決するには2人の生存者で十分であり、生存者の数を増やし続ける必要はありません

なぜエデンとサバイバーには 8:1:1 が必要なのでしょうか?

まず、これは主に弱い生成理論に基づいており、ほとんどのオブジェクトはリサイクルされるため、Survivor 空間はそれほど大きくする必要はありません。一連のパフォーマンス テストの結果、この比率が最適であることがわかり、この比率を選択しました。

マーク照合アルゴリズム

マークコピーアルゴリズムは、オブジェクトの生存率が高い場合多くのコピー操作が必要となり、効率が低下するため、主に新世代向けであり、さらに重要なことに、生存オブジェクトの保存に使用されるスペースが比較的小さいためです。ほとんどのガベージ コレクション中にオブジェクトの生存率が高い場合、割り当てを保証するために追加の領域が必要になります

マーキング分類アルゴリズムは、古い世代のオブジェクトの死亡特性に合わせて設計されています。アルゴリズムはのとおりです。マーキング プロセスは依然として「マーククリア アルゴリズム」と同じですが、その後のステップでは、リサイクル可能なオブジェクトを直接クリーンアップするのではなく、残っているすべてのオブジェクトをメモリ空間の一端に移動します。境界の外側のメモリを直接クリーンアップするため、メモリの断片化の問題は発生しません。

マークスイープアルゴリズムとマーク照合アルゴリズムの本質的な違いは、前者は非移動リサイクルアルゴリズムであるのに対し、後者は移動リサイクルアルゴリズムであることです。

HotSpot アルゴリズムの実装の詳細

ルートノードの列挙

ルート ノードの列挙ステップ中にユーザー スレッドを一時停止する必要があります。これにより、以前のメモリのデフラグと同様の「Stop The World」問題が発生します。分析結果の正確性を保証するために、ルート ノード セットのオブジェクト参照関係が分析プロセス中に変更されないように、一貫性を保証できるシステム空間のスナップショットで実行する必要があります。

現在、主流の JVM は正確なメモリ管理つまり、メモリ内の特定の場所にある特定のデータ型を明確に知ることができます。たとえば、整数が特定のメモリ アドレスを指す参照型なのか、それとも単なる整数なのかを知ることができます。

したがって、仮想マシンにはオブジェクト参照の保存場所を直接取得する方法が必要です。HotSpot は、この目的を達成するためにOopMapと呼ばれる一連のデータ構造を使用します。これについては、次で説明します。特定の場所(つまり、以下の安全点) スタックおよびレジスタ上のどの位置が参照であるかを記録します。このようにして、コレクターはスキャン時にこの情報を直接知ることができ、メソッド領域や他の GC ルートから検索を開始する必要がありません。

安全な場所

OopMap の支援により、HotSpot は GC ルートの列挙を迅速かつ正確に完了できます。

ただし、参照関係が変化する、つまりOopMapの内容が変化する命令が多く、命令ごとに対応するOopMapを生成すると余分な領域が必要となるため、HotSpotはその情報のみを記録します。
「特定の場所」。これらの場所はセーフポイントと呼ばれます。セーフ ポイントの設定では、ガベージ コレクションのために一時停止する前に、コード命令フローがセーフ ポイントまで実行される必要もあります。

メモリーセットとカードリスト

世代理論について話すとき、オブジェクトの世代間参照によって引き起こされる問題を解決するために、古い世代全体が GC ルート スキャン範囲に追加されるのを避けるために、メモリ セットデータ構造が新しい世代に導入されたことが述べられました。実際、新世代と旧世代の間の世代間参照の問題に加えて、部分領域コレクション (部分 GC) 動作に関与するすべてのガベージ コレクターは、同じ世代間参照の問題に直面することになります。

メモリ セットは、非収集領域から収集領域へのポインタのセットを記録するために使用される抽象データ構造です最も単純な実装は、このデータ構造に世代間参照を持つすべてのオブジェクトを記録することです。

ただし、ガベージ コレクションのシナリオでは、コレクターは特定の非コレクション領域にコレクション領域へのポインターがあるかどうかだけを知る必要があり、これらの世代間ポインターの特定の情報を知る必要はないため、すべてのレコードにはクロスが含まれます。 -世代参照。オブジェクトは非常に冗長であり、スペースの無駄になります。より粗いレコード粒度を選択できます。

ワード長精度: 各レコードは1 マシン語長まで正確であり、ワードに世代間ポインタ
オブジェクトが含まれていることを示します。精度: 各レコードはオブジェクトに対して正確であり、オブジェクト内にクロスを含むフィールドがあることを示します。世代ポインタ。
精度: 各レコードは正確です。メモリ領域への、世代間ポインタを含む領域にオブジェクトがあることを示します。

このうち、「カード精度」ソリューションとは、カード テーブルと呼ばれる方法を使用してメモリ セットを実装することを指し、これは現在最も一般的に使用されているメモリ セット実装形式でもあります。HotSpot のデフォルトのカード テーブル マーキング ロジックはバイト配列を使用します。

CARD_TABLE[this.address >> 9] = 0;

バイト配列 CARD_TABLE の各要素は、カード ページと呼ばれる、それが表すメモリ領域内の特定のサイズのメモリ ブロックに対応します一般に、カード ページのサイズは 2 の N 乗バイトですが、上記のコードから、HotSpot で使用されるカード ページは 2 の 9 乗バイト、つまり 512 バイトであることがわかります (アドレスを右にシフトします)。 9 ビットを使用してカード テーブル配列の対応する要素を取得すると、0 ~ 511 B のアドレスは 9 ビット右にシフトするとすべて 0 になり、これらのアドレス 0 ~ 511 B が同じカード ページ番号 0 に対応することを示します。したがって、カードのページ サイズは 512 B)

カード ページ内の 1 つ以上のオブジェクトのフィールドに世代間ポインタがある限り、対応するカード テーブル配列要素の値は 1 であり、これはダーティであると言われます。それ以外の場合は、0 としてマークされます。

ガベージ コレクション中に、カード テーブル内のダーティ要素をフィルターで除外する限り、どのカード ページ メモリ ブロックに世代間ポインタが含まれているかを簡単に見つけることができます (つまり、これらのポインタは、現在ガベージ コレクションされていない他の領域からのものです) 、ただし、これらは現在のガベージ コレクション領域内のオブジェクトへのポイントであるため、これらのポインターを GC ルートに追加してから、GC ルートに追加して一緒にスキャンする必要があります。

ライトバリア

では、カードテーブルの要素をどのように維持するのでしょうか? カード テーブル要素がダーティになるのは、他の世代領域のオブジェクトがこの領域のオブジェクトを参照するときです。HotSpot では、カード テーブルのステータスはライト バリア テクノロジーによって維持されます

書き込みバリアは、仮想マシン レベルでの「参照型フィールドの割り当て」アクションの AOP の側面として見ることができます参照オブジェクトを割り当てるとき、プログラムが追加のアクションを実行するために循環 (Around) 通知が生成されます。割り当て前の書き込みバリアの部分はプレライト バリアと呼ばれ、割り当て後の部分はポストと呼ばれます。 -書き込みバリア書き込みバリアが適用されると、仮想
マシンは、更新が古い世代から新しい世代への参照であるかどうかに関係なく、すべての割り当て操作に対応する命令を生成し、参照が更新されるたびに追加のオーバーヘッドが発生します。生成されますが、もちろん、このオーバーヘッドは、マイナー GC 中に古い世代全体をスキャンする場合と同じです。コストは、

さらに、カード テーブルは、同時実行性が高いシナリオでは偽共有
簡単な解決策は、無条件書き込みバリアを使用するのではなく、最初にカード テーブル マークをチェックし、マークされていない場合にのみカード テーブル要素をダーティにすることです。

if(CARD_TABLE[this.address >> 9] != 0)   CARD_TABLE[this.address] = 0;

同時到達可能性分析

到達可能性分析アルゴリズムでは、理論的には、分析を実行する前に一貫性を確保できるスナップショットにプロセス全体が基づいている必要があります。これは、ルート ノードを列挙し、オブジェクト グラフを走査するために、ユーザー スレッドの実行をプロセス全体でフリーズする必要があることを意味しますマーキング用。では、整合性を保証できるスナップショット上でオブジェクト グラフを走査する必要があるのはなぜでしょうか?

ユーザー スレッドとコレクターが同時に動作する場合、2 つの結果が生じる可能性があります: 1 つは、元々死んでいたオブジェクトが誤って生きているとマークされることです。これは許容できますが、このコレクションから逃れるオブジェクトが生成されます。浮遊ゴミそれだけです。もう 1 つは、元々生き残っていたオブジェクトを誤って Dead としてマークすることです。この間違いは非常に重大であり、プログラム エラーを引き起こす可能性があります。

3 色のマーキングの例を参考にすると、次の結論が得られます。「オブジェクトの消失」の問題は、次の 2 つの条件が同時に満たされた場合にのみ発生します。黒であることが誤って白としてマークされます:割り当て者 (ユーザー スレッドとして理解できます) 黒のオブジェクトから白のオブジェクトへの 1 つ以上の新しい参照が挿入されます;割り当て者は、灰色のオブジェクトから白のオブジェクトへの直接または間接的な参照をすべて削除します。 (黒いオブジェクトの参照関係はチェックされているため、再度チェックされないため、白いオブジェクトがそのオブジェクトによって参照され、白いオブジェクトが灰色のオブジェクトによって参照されていない場合、仮想マシンはこの参照を見つけられません関係があるため、白いオブジェクトが削除されます。実際には、黒いオブジェクトによってすでに参照されているため、これは黒いオブジェクトであると言う必要があります)

したがって、次の 2 つの条件のうち 1 つを破るだけで済みます。

  1. 増分更新(増分更新) は最初の条件を破棄します。
    黒いオブジェクトが白いオブジェクトを指す新しい参照関係を挿入すると、新しく挿入された参照関係が記録され、同時スキャンの完了後、これらの記録された参照関係は、黒いオブジェクトは次のようになります。ルート、再度スキャンします
    つまり、白いオブジェクトへの新しい参照が挿入されると、黒いオブジェクトは灰色のオブジェクトになります。
  2. 元のスナップショット(最初のスナップショット、SATB) は 2 番目の条件を破棄します:
    灰色のオブジェクトが白いオブジェクトへの参照を削除したいとき、削除される参照が記録されます。同時スキャンが完了した後、これらの記録された参照は、灰色のオブジェクトの参照になります。関係はルートであり、再度スキャンされます. つまり、参照関係が削除されているかどうかに関係なく、スキャンが開始された瞬間のオブジェクト グラフのスナップショットに基づいて検索されます。これは、と同等です灰色のオブジェクトから白色のオブジェクトへの参照は削除されません。

これら 2 つの方法により、同時スキャンを実現できますが、2 番目のスキャンではユーザー プロセスを停止する必要があります。ただし、2 回目のスキャン中にユーザー プロセスを停止するのにかかる時間は、1 回のスキャンでユーザー プロセスを停止するのにかかる時間と比較すると依然として短いです。上記の参照関係レコードの挿入または削除は、書き込みバリア
を通じて実行されます。実装されたCMS は、同時マーキングのための増分更新に基づいており、G1は元のスナップショットを使用して実装されています。

古典的なガベージコレクター

古典的なガベージコレクター

シリアル

Serial はガベージ コレクションを完了するために 1 つのプロセッサまたは 1 つのコレクション スレッドを使用するだけでなく、収集するときも、コレクションが完了するまで他のすべての作業スレッドを一時停止する必要があります。Serial はマイナー GC にコピー アルゴリズムを使用し、Serial Old はマーク
-オーガナイザー使用ますメジャー GC のアルゴリズム

パーニュー

ParNew コレクターは、本質的にはSerial コレクターのマルチスレッド同時バージョンであり、マイナー GC のコピーアルゴリズムも使用します。

パラレルスカベンジ

Parallel Scavenge は、マークコピーアルゴリズムに基づいて新しい世代を収集します。他のコレクターとは焦点が異なっているのが特徴で、CMS などのコレクターがガベージ コレクション時のユーザー スレッドの一時停止時間を可能な限り短縮することに焦点を当てているのに対し、Parallel Scavenge の目標は制御可能なスループット ( スループット ) を実現することです。 )、いわゆるスループットは、プロセッサが消費した合計時間に対する、プロセッサがユーザー コードの実行に費やした時間の比率です。

スループット = ユーザー コードの実行時間 / (ユーザー コードの実行時間 + ガベージ コレクションの実行時間) スループット = ユーザー コードの実行時間 \ /\ (ユーザー コードの実行時間 + ガベージ コレクションの実行時間)スループット=ユーザー コードの実行時間/ (ユーザー コードの実行時間  +ガベージ コレクションを実行する時間)

一時停止時間が短いほど、ユーザーと対話したり、サービス応答の品質を保証する必要があるプログラムに適しています。応答速度が優れていると、ユーザー エクスペリエンスが向上します。また、スループットが高いと、プロセッサ時間を最も効率的に使用して計算タスクを完了できます。できるだけ早くプログラムを実行します。主に、バックグラウンドで動作し、あまり多くの対話を必要としない分析タスクに適しています。

パラレルオールドこれは、 Parallel Scavenge コレクターの旧世代バージョンであり、マルチスレッドの同時収集をサポートし、マークソートアルゴリズムに基づいて実装されています。Parallel Scavenge と同様に、スループットに重点を置く

CMS

CMS (同時マーク スイープ 同時\ マーク\ スイープC o n c u r r n t M a r k Swee p )  コレクターは、回復一時停止時間を最短にすることを目標とするコレクターです。アプリケーションには、優れたユーザー インタラクション エクスペリエンスが必要です。

CMS コレクターはマークスイープアルゴリズムに基づいており、その操作プロセスは次のように分かれています。初期マーク( CMS 初期マーク CMS\ 初期\ マークCMS初期マーク  )、同時マーク( CMS同時マークCMS \同時\ マークCMS一致マーク  )コメント( CMSリマークCMS \リマーク_ _ _CMSリマーク )、同時スイープ(CMS 同時スイープ CMS\同時\スイープCMS同時スイープ  ) _ _ _ _ _ _ _ _ _

  • 初期マーキングは、 GC ルートが直接関連付けることができるオブジェクトのみをマークします。これは非常に高速です。ユーザースレッドを停止する必要があります (「Stop The World」)
  • 同時マーキングは、 GC ルートの直接関連付けられたオブジェクトから開始してオブジェクト グラフ全体を走査するプロセスです。このプロセスには時間がかかりますが、ユーザー スレッドを停止する必要はありません。ガベージ コレクション スレッドと同時に実行できます (増分更新のため)このメソッドは、「オブジェクトが死んだとき」の問題を解決するために使用されます)
  • リマーキングは、同時マーキング中にユーザー スレッドの継続的な操作によって変更されたオブジェクトのその部分のマーキング レコードを修正することです。これは増分更新によって操作され、ユーザー スレッドも一時停止する必要があります。
  • 同時クリーンアップフェーズ クリーンアップは、マーキング フェーズで判断された死んだオブジェクトを削除します。生きたオブジェクトを移動する必要がなく、死んだオブジェクトは再参照されないため、ユーザー スレッドと並行して実行できます(マーク クリア アルゴリズム)

CMS は、HotSpot 仮想マシンによる低一時停止の追求に成功した最初の試みですが、少なくとも次の 3 つの明らかな欠点があります。

  • プロセッサリソースに非常に敏感です. 低一時停止を追求するため、同時実行性を重視して設計されています. 同時実行段階では、ユーザースレッドを一時停止させないという目的は達成しますが、スレッドの一部を占有します。プロセッサの計算能力の一部であり、アプリケーションの速度が低下し、全体のスループットに影響します。

  • CMS は「浮遊ガベージ」を処理できないため、「同時モード障害」障害が発生し、別の完全な「Stop The World」フル GC が発生する可能性があります。「フローティング ガベージ」とは、CMS の同時マーキングおよび同時クリーニング フェーズ中に、ユーザー スレッドがまだ実行中であることを意味します。当然のことながら、プログラムの実行中は新しいガベージ オブジェクトが生成され続けますが、ガベージ オブジェクトのこの部分が表示されます。マーキング プロセスが完了すると、CMS は現在のコレクションでそれらを処理できず、次のガベージ コレクションでクリーンアップされるまで待つ必要があります。この部分のガベージは「フローティング ガベージ」と呼ばれます。

    また、ユーザー スレッドはガベージ コレクション フェーズ中に実行し続ける必要があるため、ユーザー スレッドが使用できるように十分なメモリ領域を予約する必要があるため、CMS コレクターは、以前の他のコレクターのように古い世代がほぼいっぱいになるまで待つことができません。コレクションの場合、同時収集中のプログラム操作用にスペースの一部を予約する必要があります。つまり、すべてのスペースが使用されるまで待つのではなく、古い世代の使用済みスペースが特定のしきい値に達したときにガベージ コレクションが実行されます。

  • CMS はマークアンドスイープ アルゴリズムに基づいているため、コレクションの最後に大量のスペースの断片化が生成されます。

ガベージファースト (G1)

滞留時間モデル

G1 は、部分コレクション用のコレクターリージョンベースのメモリ レイアウト形式の設計アイデアを先駆けて開発しました。領域ベースのヒープ メモリ レイアウトは、G1 が「休止時間モデル」を確立するという目標を達成するための鍵です。「休止時間モデル」とは、長さ M の時間セグメント内でガベージ コレクションに費やされる大量の時間をサポートできることを意味します。ミリ秒。確率は目標の N ミリ秒を超えません。

混合 GC

G1 コレクターが登場する前のすべてのコレクターでは、ガベージ コレクションの範囲は新しい世代全体、古い世代全体、または Java ヒープ全体のいずれかでしたが、G1 はヒープの任意の部分に対してコレクション (コレクション セット) を形成できます。メモリ. 、CSet) をリサイクルする場合、測定基準はもはやどの世代に属しているかではなくどのメモリ部分が最大量のガベージを保存し、リサイクルの利点が最も大きいかです。これがG1 コレクタの混合 GCモードです。

世代領域分割の固定サイズと固定数に固執する代わりに、連続した Java ヒープは同じサイズの複数の独立した領域、すなわちリージョンに分割され、各リージョンは新世代の Eden 空間と Survivor 空間の役割を果たすことができます。 、または古い世代のスペースでは、コレクターはさまざまな戦略を使用して、さまざまな役割を果たすリージョンを処理できます。

リージョンには、大きなオブジェクトを格納するために特別に使用される特別なタイプの巨大な領域もあります。G1 では、サイズがリージョンの容量の半分を超えるオブジェクトは大きなオブジェクトとして判断できると考えています。リージョン全体の容量は、N 個の連続する巨大なリージョンに保存されます

リサイクル方法

G1 には新世代と旧世代という概念がまだ残っていますが、新世代と旧世代は固定されたものではなく、連続する必要のない動的な領域の集合体です。

G1 コレクターが予測可能な停止時間モデルを確立できる理由は、G1 コレクターが各リージョンで蓄積されたゴミの「価値」 (リサイクルによって得られるスペースの量とリサイクルに必要な時間) を追跡できるようにするためです。 , そして、バックグラウンドで優先順位リストを維持します。毎回、ユーザーが設定した収集一時停止時間の許容値 (パラメーターを使用して指定、デフォルトは 200 ミリ秒) に従って、回復値の増加が最も大きいリージョンが優先されます。-XX:MaxGCPauseMillsこれは、その名も「ガベージファースト」

このようにリージョンを使用してメモリ空間を分割し、リージョンを優先的にリサイクルすることで、G1 コレクターは限られた時間内で可能な限り最高の収集効率を得ることができます。

いくつかの詳細の処理

  1. G1 コレクターでのメモリ セットの適用はより複雑です。各リージョンは独自のメモリ セットを維持します。これらのメモリ セットは、他のリージョンが指すポインタを記録し、これらのポインタが含まれるカード ページの範囲をマークします。これは本質的にハッシュ テーブルであり、キーは他のリージョンの開始アドレス、値はセット、そこに格納されている要素はカード テーブルのインデックス番号です。

  2. G1 コレクターは、独自のスナップショットアルゴリズムを使用して、同時マーキング フェーズ中にユーザー スレッドによってオブジェクト グラフ構造が損傷する可能性があるという問題を解決します。さらに、ガベージ コレクションはユーザー スレッドの影響を受け、新しく作成されたスレッドのメモリ割り当てにも反映されます。リサイクル プロセス中にオブジェクトが作成されます。プログラムは実行し続ける必要があります。新しいオブジェクトは確実に作成され続けます。G1 は、領域ごとにTAMS (Top at Mark Start) という名前の 2 つのポインタを設計し、領域内のスペースの一部を新しいオブジェクト用に分割します。同時リサイクル プロセス中の割り当て 同時実行 リサイクル中に新しく割り当てられたオブジェクトのアドレスは、これら 2 つのポインタ位置より上にある必要があります。

    G1 コレクターは、このアドレスより上のオブジェクトをデフォルトで暗黙的にマークします。つまり、オブジェクトはデフォルトで生きており、リサイクル範囲には含まれません。CMS の「同時モード障害」と同様に、失敗するとフル GC が発生します。メモリの再利用の速度がメモリ割り当ての速度に追いつかない場合、G1 もユーザー スレッドの実行を強制的にフリーズさせてフル GC を引き起こします。

運用プロセス

運用プロセスは大きく以下の4つのステップに分かれます。

  1. 初期マーキング: GC ルートが直接関連付けることができるオブジェクトのみをマークし、ユーザー スレッドの同時実行の次のフェーズで使用可能なリージョンに新しいオブジェクトが正しく割り当てられるように、TAMS ポインターの値を変更します。
  2. 同時マーキング: GC ルートから開始して到達可能性分析を実行して、リサイクルされるオブジェクトを見つけます。オブジェクト グラフ スキャンの完了後、同時実行中に SATB によって記録された参照変更のあるオブジェクトを再処理する必要があります。
  3. 最終マーキング: 並行処理フェーズの終了時に取得される SATB レコード
  4. スクリーニングとリサイクル(ライブデータのカウントと避難):リージョンの統計を更新し、各リージョンのリサイクル価値とコストを分類し、ユーザーが予想する停止時間に基づいてリサイクル計画を策定します。リージョンを使用してリサイクル コレクションを形成します。次に、リサイクルすることを決定したリージョンの一部の残りのオブジェクトを空のリージョンにコピーし、古いリージョン内のすべてのスペースをクリーンアップします。
    これにはライブ オブジェクトの移動が含まれるため、ユーザー スレッドは一時停止され、複数のコレクター スレッドによって並行して完了する必要があります。

ポーズ時間の設定について

ユーザーが希望する一時停止時間を指定できる機能は G1 の非常に強力な機能ですが、「期待値」の設定は現実的でなければなりません。

一時停止時間はできるだけ短くすることが予想されますが、一時停止時間が非常に短く調整されると、一時停止の目標時間が短すぎるため、毎回選択できる収集されたコレクションが 1 つのみを占有するだけになる可能性があります。少量のヒープ メモリの一部、つまり各クリーニングのヒープ メモリ領域が少なく、各クリーニングの効率が高くなく、コレクタの収集速度がアロケータの速度に追いつかない場合、ガベージはゆっくりと蓄積され、最終的にはヒープがいっぱいになり、フル GC が実際にパフォーマンスを低下させる原因となるため、通常は、予想される一時停止時間を 1 ~ 200 ミリ秒または 2 ~ 300 ミリ秒に設定する方が合理的です。

コレクタ技術開発のマイルストーン

G1 以降、最先端のガベージ コレクターの設計方向は常に、Java ヒープ全体を一度に追求するのではなく、アプリケーションに対応できるメモリ割り当て率を追求するように変化してきました。アプリケーションが割り当て中です。コレクションの速度がオブジェクト割り当ての速度に追いつくことができる限り、すべてが完璧に機能します。
この新しいコレクタ設計のアイデアは、エンジニアリング実装の観点から G1 から生まれたため、G1 はコレクタ テクノロジの開発です。マイルストーン

個人的な理解: この 2 つの設計の方向性は動的と静的であると言え、前者は一時停止時間などの設定に基づいて毎回収集されるタスクの量を動的に調整できますが、後者は情報を考慮しません。毎回ヒープをクリーンアップします。ダイナミックなアプローチにより、コレクターはより柔軟で「賢く」なります。

G1 と CMS の比較

  1. G1 と CMS はどちらも一時停止時間の制御に細心の注意を払っています。

  2. CMS と比較して、G1 には多くの利点があります。最大一時停止時間の指定機能、領域のメモリ レイアウト、収益に基づいたコレクションの動的な決定などの革新的な設計に加えて、従来のアルゴリズムの理論的観点から、CMS は「マーククリア」を使用します。 G1 全体としてはmark-organize 」アルゴリズムに基づいて実装されますが、ローカルな観点、つまり 2 つのリージョン間では「 mark-copy 」アルゴリズムに基づいて実装されます。両方のアルゴリズムは、次のことを意味します。 G1 の操作中にメモリは生成されません。ガベージ コレクションの完了後、フラグメンテーションによって定期的に使用可能なメモリが提供され、この機能はプログラムの長期実行に役立ちます。プログラムが大きなオブジェクトにメモリを割り当てると、連続したメモリ領域を見つけることができないため、次のコレクションを事前にトリガーすることを強制するのは簡単ではありません。

  3. もちろん、G1 には、ユーザー プログラム実行時のガベージ コレクションのためのメモリ使用量や、プログラム実行時の追加実行負荷が CMS よりも高いなど、G1 と比較すると弱点もあります。

    • メモリ使用量の点では、どちらもカード テーブルを使用して世代間ポインタを処理しますが、G1 のカード テーブルの実装はより複雑で、各リージョンにはその役割に関係なくカード テーブルが必要です。その結果、G1 のメモリ セットが大量に消費されます。比較すると、CMS カード テーブルは非常にシンプルで、コピーが 1 つだけあり、古い世代から新しい世代への参照を処理するだけで済み、その逆は不要です。 "。不安定で、参照が頻繁に変更されるため、新世代から他の領域への参照のメンテナンスのオーバーヘッドを節約するのは非常にコスト効率が高くなります。もちろん、コストがかかるのは CMS で古い GC が発生したときです (CMS のみが古いものを持っています
      )すべてのコレクターの中の古い世代の GC )、CMS は新世代から旧世代までのカード テーブルを維持せず、G1 カード テーブルはリージョンに基づいて維持されるため、新世代全体が GC ルートとしてスキャンされる必要があります。地域が現在旧世代の役割を果たしているかどうかに関係なく、新世代のキャラクターはすべて、非コレクション世代からコレクション世代を指すカードリストを持っています。

    • たとえば、実行負荷の観点から見ると、どちらもカード テーブルを維持するためにポストライト バリアを使用します。また、元のスナップショット検索アルゴリズムを実装するために、G1 は同時実行中のポインタの変更を追跡するためにプレライト バリアも使用する必要があります。元のスナップショットは、スキャン前に参照を記録する必要があります。そうしないと、スキャン後に参照が実際に削除された場合は見つかりません。スキャン後に参照が実際に削除された場合にのみ、参照を記録できます)。 CMS の書き込みバリアよりも優れています 実装はより複雑で時間がかかります

おすすめ

転載: blog.csdn.net/Pacifica_/article/details/123114023