9 ガベージファースト・ガベージ・コレクタ

この項では、ガベージファースト(G1)ガベージ・コレクタ(GC)について説明します。

ガベージファースト・ガベージ・コレクタの概要

ガベージファースト(G1)ガベージ・コレクタは、大規模なメモリーを保持するマルチプロセッサ・マシンを対象としています。これはガベージ・コレクション一時停止時間目標を高い確率で満たそうとする一方、構成の必要がなく、高いスループットを実現します。G1はレイテンシとスループットの間で最適なバランスを取ることを目標とし、現在のターゲット・アプリケーションと次の機能が含まれる環境を使用します。

  • 最大10GBまたはそれ以上のヒープ・サイズ(Javaヒープの50%超がライブ・データで占められている)。
  • 時間が経つと大きく変化する可能性のあるオブジェクトの割当て率および昇格率。
  • ヒープ内の大量の断片化。
  • 数百ミリ秒以内の予測可能な一時停止ターゲット目標(長時間のガベージ・コレクションの一時停止を回避)。

G1はコンカレント・マーク・スイープ(CMS)コレクタのかわりとなるものです。これはデフォルトのコレクタでもあります。

G1コレクタは高いパフォーマンスを実現し、次の項で説明されているいくつかの方法で一時停止時間目標を満たそうとします。

G1の有効化

ガベージファースト・ガベージ・コレクタはデフォルト・コレクタであるため、通常、追加の処理を実行する必要はありません。コマンド行で-XX:+UseG1GCを指定すると、これを明示的に有効化できます。

基本概念

G1は、世代別、増分的、並列型、モーストリ・コンカレント、stop-the-world型で退避型のガベージ・コレクタであり、stop-the-world型の一時停止ごとに一時停止時間目標を監視します。他のコレクタと同様に、G1では(仮想の)若い世代と古い世代にヒープを分割します。領域の回収は若い世代に行う方が効果的であるため、若い世代に対して集中的に行われますが、古い世代に対して行われることもあります

一部の操作は常にstop-the-world型の一時停止で実行され、スループットを向上させます。グローバル・マーキングのようなヒープ全体の操作など、アプリケーションを停止すると時間がかかる他の操作は、アプリケーションと同時に並列実行されます。領域の回収にstop-the-world型の一時停止を短くするために、G1は領域回収を徐々に増分しながら並列実行します。G1では、前回のアプリケーションの動作とガベージ・コレクションの一時停止に関する情報をトラッキングして予測し、関連するコストのモデルを作成します。この情報を使用して、一時停止で実行される作業のサイズを判定します。たとえば、G1では、最も効率的な場所の領域を最初に回収します(ガベージでほぼいっぱいになっている領域を最初に行うため、このように呼ばれています)。

G1では、主に退避を使用して領域を回収します。選択したメモリー領域内で見つかったライブ・オブジェクトは新しいメモリー領域にコピーされ、プロセスでこれらを圧縮します。退避が完了すると、ライブ・オブジェクトで占有されていた領域はアプリケーションによる割当てに再使用されます。

ガベージファースト・コレクタは、リアルタイム・コレクタではありません。時間をかけて設定された一時停止時間目標を高い確率で満たそうとしますが、特定の一時停止では必ずしも絶対確実ではありません。

ヒープ・レイアウト

G1ではヒープを均等サイズのヒープ・リージョン・セットに分割し、図9-1に示すように、それぞれは連続する一連の仮想メモリーです。リージョンとは、メモリー割当ておよびメモリー回収の単位です。常に、これらの各リージョンは空であるか(明るいグレー)、特定の世代(若いまたは古い世代)に割り当てることができます。メモリーに対するリクエストが発生すると、メモリー・マネージャが空きのリージョンを割り当てます。メモリー・マネージャはこれらをある世代に割り当て、次に空き領域としてアプリケーションにこれらを返却し、そこに自分自身を割り当てることができます。

図9-1 G1ガベージ・コレクタのヒープ・レイアウト

図9-1の説明が続きます
「図9-1 G1ガベージ・コレクタのヒープ・レイアウト」の説明

若い世代には、Edenリージョン(赤)とSurvivorリージョン(Sが付いた赤)が含まれています。これらのリージョンは、他のコレクタの連続的な各領域と同じ機能を備えており、G1において通常、これらのリージョンはメモリー内で連続的でないパターンで配置されている点が異なります。古いリージョン(水色)は古い世代を構成しています。古い世代のリージョンは複数のリージョンにまたがるオブジェクトに対してサイズが大きくなることがあります(Hが付いた水色)。

アプリケーションは、古い世代に属しているために直接割り当てられている大型オブジェクトを除いて、常に若い世代(Edenリージョン)に割り当てられます。

G1ガベージ・コレクションの一時停止では、全体として若い世代の領域を回収し、任意のコレクションの一時停止で古い世代のリージョンの領域を追加で回収できます。一時停止中に、G1はオブジェクトをこのコレクション・セットからヒープ内の1つ以上の異なるリージョンにコピーします。オブジェクトの移動先リージョンはそのオブジェクトの移動元リージョンによって異なります。若い世代全体がSurvivorまたは古いリージョンにコピーされ、古いリージョンのオブジェクトが、エージングを使用して他の異なる古いリージョンにコピーされます。

ガベージ・コレクション・サイクル

高いレベルで、G1コレクタは2つのフェーズを交代させます。若い世代のみのフェーズには、現在使用可能なメモリーを古い世代のオブジェクトで徐々に満たすガベージ・コレクションが含まれています。領域回収フェーズでは、G1は増分しながら古い世代の領域を回収し、さらに若い世代に対応します。その後、このサイクルでは若い世代のみのフェーズを再度開始します。

図9-2に、このサイクルの概要および発生する可能性のあるガベージ・コレクションの一時停止の順序の例を示します。

図9-2 ガベージ・コレクションのサイクルの概要

図9-2の説明が続きます
「図9-2 ガベージ・コレクションのサイクルの概要」の説明

次のリストに、フェーズ、一時停止、G1ガベージ・コレクション・サイクルのフェーズ間での推移を詳細に示します。

  1. 若い世代のみのフェーズ: このフェーズは、オブジェクトを古い世代に昇格させる、いくつかの通常の若い世代のコレクションから開始します。若い世代のみのフェーズと領域回収フェーズ間の推移は、古い世代の占有率が特定のしきい値(開始ヒープ占有率しきい値)に達したときにのみ、開始します。この時点で、G1は通常の若い世代のコレクションのかわりに、コンカレント開始の若い世代のコレクションをスケジュールします。 

    • コンカレント開始: このタイプのコレクションはマーキング・プロセスを開始し、通常の若い世代のコレクションも実行します。コンカレント・マーキングでは、次の領域回収フェーズで維持される、古い世代のリージョンの現在到達可能なすべての(ライブ)オブジェクトを判定します。コレクションのマーキングが完全に終了していない場合、通常の若い世代のコレクションが発生することがあります。マーキングは、再マークとクリーン・アップの2つの固有のstop-the-world型の一時停止で終了します。 

    • 再マーク: この一時停止は、マーキング自体をファイナライズし、グローバル参照処理とクラスのアップロードを実行し、空のリージョンを完全に回収して、内部データ構造をクリーン・アップします。再マークとクリーン・アップの間に、G1は、選択した古い世代のリージョン内の空き領域を後で回収できるようにする情報を同時に計算し、この情報がクリーン・アップの一時停止でファイナライズされます。

    • クリーン・アップ: この一時停止では、領域回収フェーズが実際に後に続くかどうかを判定します。領域回収フェーズが後に続いた場合、この若い世代のみのフェーズは、単一の混合準備の若い世代のコレクションで完了します。 

  2. 領域回収フェーズ: このフェーズは複数の混合コレクションから構成され、若い世代のリージョンに加えて、古い世代のリージョン・セットのライブ・オブジェクトも退避します。領域回収フェーズは、古い世代のリージョンをこれ以上退避しても、十分な空き領域を作り出せないとG1が判断した場合に終了します。

領域回収後、コレクション・サイクルが別の若い世代のみのフェーズから再度開始します。バックアップとして、アプリケーションがライブ情報の収集中にメモリーを使い果たした場合、G1では他のコレクタのようなstop-the-world型のインプレース・フル・ヒープ圧縮(フルGC)を実行します。

ガベージファーストの内部

この項では、ガベージファースト(G1)ガベージ・コレクタの重要な詳細情報について説明します。

開始ヒープ占有率の決定

開始ヒープ占有率(IHOP)は、初期マーク・コレクションがトリガーされるしきい値で、古い世代のサイズの割合として定義されます。

G1では、デフォルトで、マーキングにかかった時間とマーキング・サイクル時に古い世代に通常割り当てられるメモリーの量を監視することで、最適なIHOPを自動的に判定します。この機能は、適応型IHOPと呼ばれます。この機能がアクティブな場合、オプション-XX:InitiatingHeapOccupancyPercentは、開始ヒープ占有率しきい値を予測するのに十分な監視結果がなければ、現在の古い世代のサイズの割合として初期値を判定します。オプション-XX:-G1UseAdaptiveIHOPを使用して、G1のこの動作をオフにします。この場合、-XX:InitiatingHeapOccupancyPercentの値は常にこのしきい値を判定します。

内部的に、適応型IHOPは開始ヒープ占有率を設定しようとします。これにより、古い世代の占有率が現在の古い世代の最大サイズから余分なバッファとして-XX:G1HeapReservePercentの値を差し引いた値に達したときに、領域回収フェーズの最初の混合ガベージ・コレクションが開始します。

マーキング

G1マーキングでは、Snapshot-At-The-Beginning (SATB)と呼ばれるアルゴリズムを使用します。マーキングの開始時にライブだったすべてのオブジェクトがマーキングの残りに対してライブとみなされている場合、初期マークの一時停止時にヒープの仮想スナップショットを取得します。これは、マーキング時に寿命を終えた(到達不能な)オブジェクトが領域回収のためにライブとしてみなされているということです(一部例外あり)。他のコレクタと比較すると、これによって追加のメモリーが不適切に保持される場合があります。ただし、SATBでは再マークの一時停止時に適切なレイテンシが発生する可能性があります。このマーキング時にライブ・オブジェクトとみなされたオブジェクトは、次回のマーキング時に回収されます。マーキングでの問題の詳細は、「ガベージファースト・ガベージ・コレクタのチューニング」のトピックを参照してください。

非常にタイトなヒープの状況における動作

退避でコピーする領域を見つけられないほど、アプリケーションが大量のメモリーを保持している場合、退避は失敗します。退避が失敗したということは、G1で、まだ移動していないオブジェクトをコピーせずに、すでに新しい場所に移動したオブジェクトを保持して現在のガベージ・コレクションを完了しようとしており、オブジェクト間の参照のみを調整していることを意味します。退避が失敗すると、追加のオーバーヘッドが発生することがありますが、一般的に他の若いコレクションと速度は変わりません。退避が失敗したガベージ・コレクションの後で、G1はアプリケーションを通常どおりに再開し、他の措置は取りません。G1では、退避の失敗はガベージ・コレクションの終了間際に発生すると想定しています。すなわち、ほとんどのオブジェクトはすでに移動していて、マーキングが完了して領域の回収が開始するまで、アプリケーションの実行を続けるのに十分な領域が残っているということです。

この想定が当てはまらない場合、G1は最終的にフルGCをスケジュールします。このタイプのコレクションは、ヒープ全体のインプレース圧縮を実行します。これは時間がかかることがあります。

割当ての失敗またはメモリーからの通知の前のフルGCの問題の詳細は、「ガベージファースト・ガベージ・コレクタのチューニング」のトピックを参照してください。

大型オブジェクト

大型オブジェクトとは、リージョンの半分のサイズ以上のオブジェクトです。「G1 GCのエルゴノミック・デフォルト」の項で記述されているように、-XX:G1HeapRegionSizeオプションを使用して設定されないかぎり、現在のリージョン・サイズはエルゴノミックに決定されます。

これらの大型オブジェクトは次のように扱われることがあります。
  • すべての大型オブジェクトは、古い世代の連続的なリージョンの順序に従って割り当てられます。オブジェクト自体の開始は、常に、最初のリージョンの開始時にその順序で行われます。オブジェクト全体が回収されるまで、順序内の最後のリージョンで残っている領域が割当てのために失われます。
  • 一般的に、大型オブジェクトは到達不能になった場合、クリーン・アップの一時停止中またはフルGC中のマーキングの終了時にのみ回収できます。ただし、プリミティブなタイプの配列(例: bool、すべての種類の整数、浮動小数点の値)の大型オブジェクト用に特殊なプロビジョニングがあります。G1では、大型オブジェクトがガベージ・コレクションの一時停止で多数のオブジェクトから参照されなくなった場合、大型オブジェクトを回収しようとします。この動作はデフォルトで有効になっていますが、オプション-XX:G1EagerReclaimHumongousObjectsを指定すると無効化できます。
  • 大型オブジェクトの割当てによってガベージ・コレクションの一時停止が早く発生することがあります。G1は、大型オブジェクトの割当てごとに開始ヒープ占有率しきい値をチェックし、現在の占有率がしきい値を超えると、即座に初期マークの若いコレクションを強制できます。
  • 大型オブジェクトは、フルGC時でも移動しません。このため、フルGCが早く低速化するか、リージョン領域の断片化のために空き領域を大量に保持したまま予期しないメモリー不足の状態になる可能性があります。

若い世代のみのフェーズの世代のサイズ設定

若い世代のみのフェーズで、収集するリージョンのセット(コレクション・セット)は若い世代のリージョンのみから構成されます。G1は、通常の若い世代のコレクションの終了時に、次のミューテータ・フェーズ用に若い世代のサイズを決定します。このように、G1は、実際の一時停止時間の長時間に及ぶ監視に基づいて、 -XX:MaxGCPauseTimeMillis-XX:PauseTimeIntervalMillisを使用して設定された一時停止時間目標を満たすことができます。同様のサイズの若い世代が退避するのにどのくらい時間がかかるかを考慮します。これには、コレクション時にコピーする必要があったオブジェクト数やこれらのオブジェクトが相互接続された方法などの情報が含まれます。

制限がなければ、G1は、-XX:G1NewSizePercent-XX:G1MaxNewSizePercentが一時停止時間を満たすために決定する値の間で適応させながら若い世代のサイズを決定します。長い一時停止を修正する方法の詳細は、「ガベージファースト・ガベージ・コレクタのチューニング」を参照してください。

領域回収フェーズの世代のサイズ設定

領域回収フェーズ時に、G1では、1回のガベージ・コレクションの一時停止で古い世代で回収される領域の量を最大化しようとします。若い世代のサイズは許可される最小限に設定され、通常は、-XX:G1NewSizePercentで決定され、リージョンをさらに追加することにより一時停止時間目標を超えると、G1が判定するまで、領域を回収する古い世代のリージョンが追加されます。特定のガベージ・コレクションの一時停止で、G1は古い世代のリージョンを回収の効率性の高い順に追加します。残りの使用可能な時間に最後のコレクション・セットを取得します。

ガベージ・コレクションごとに取得する古い世代のリージョン数は、収集する候補の古い世代のリージョン数で下限が設定されていて(コレクション・セットの候補リージョン)、 -XX:G1MixedGCCountTargetで決定される領域回収フェーズの長さで分割されます。コレクション・セットの候補リージョンは、フェーズの開始時に-XX:G1MixedGCLiveThresholdPercentより低い占有率のすべての古い世代のリージョンです。

コレクション・セットの候補リージョンで回収可能な領域の残りの量が-XX:G1HeapWastePercentによって設定された割合より低い場合に、フェーズが終了します。

G1が使用する古い世代のリージョンの数および長い混合コレクションの一時停止を回避する方法の詳細は、「ガベージファースト・ガベージ・コレクタのチューニング」を参照してください。

G1 GCのエルゴノミック・デフォルト

このトピックでは、G1に固有の重要なデフォルト設定とそのデフォルト値の概要について説明します。追加のオプションを指定せずにG1を使用して想定される動作およびリソースの使用状況の概要についても説明します。

表9-1 G1 GCのエルゴノミック・デフォルト

オプションとデフォルト値 説明

-XX:MaxGCPauseMillis=200

最大一時停止時間の目標。

-XX:GCPauseTimeInterval=<ergo>

最大一時停止時間間隔の目標。デフォルトではG1は目標を設定せず、極端な場合にガベージ・コレクションを連続的に実行します。

-XX:ParallelGCThreads=<ergo>

ガベージ・コレクションの一時停止時に並行作業に使用するスレッドの最大数。これは、VMが実行されるコンピュータの使用可能なスレッド数から導出されます。たとえば、プロセスに使用可能なCPUスレッド数が8以下の場合、これを使用します。これ以外の場合、最終スレッド数よりも大きいスレッドの8分の5を追加します。

一時停止ごとに開始時に使用されるスレッドの最大数は最大合計ヒープ・サイズによってさらに制限されます。つまり、G1が -XX:HeapSizePerGCThread単位当たりのJavaヒープ容量に対して使用できるスレッドは1つのみです。

-XX:ConcGCThreads=<ergo> 

並行作業に使用されるスレッドの最大数。デフォルトでは、この値は-XX:ParallelGCThreads を4で割った値です。

-XX:+G1UseAdaptiveIHOP

-XX:InitiatingHeapOccupancyPercent=45

開始ヒープ占有率を制御するデフォルトは、その値の適応的な決定がオンになっていて、マーク開始のしきい値として、最初のいくつかのコレクション・サイクルでG1が古い世代の45%の占有率を使用していることを示します。

-XX:G1HeapRegionSize=<ergo> 

初期および最大ヒープ・サイズに基づいたヒープ・リージョン・サイズの設定。したがって、そのヒープには2048個のヒープ・リージョンが含まれています。ヒープ・リージョンのサイズは1から32MBに変化する可能性があり、2の累乗である必要があります。

-XX:G1NewSizePercent=5

-XX:G1MaxNewSizePercent=60

若い世代の合計サイズは、使用中の現在のJavaヒープの割合であるため、これらの2つの値で変化します。

-XX:G1HeapWastePercent=5

コレクション・セットの候補内の許容される回収されない領域の割合。コレクション・セットの候補内の空き領域がこれより低い場合、G1は領域回収フェーズを停止します。

-XX:G1MixedGCCountTarget=8

多数のコレクション内で領域回収フェーズの想定される長さ。

-XX:G1MixedGCLiveThresholdPercent=85

この割合より高い占有率のライブ・オブジェクトを含む古い世代のリージョンは、この領域回収フェーズで収集されません。

注意:

<ergo>は、実際の値が環境によってエルゴノミックに決定されるという意味です。

他のコレクタとの比較

これは、G1と他のコレクタとの主な相違点のサマリーです。

  • パラレルGCは、全体として古い世代の領域を圧縮および回収できます。G1では、複数のより短いコレクション間でこの作業を分散します。これにより、一時停止時間が短縮されますがスループットが犠牲になる可能性があります。
  • CMSと同様に、G1では古い世代の領域回収の一部を同時に実行します。ただし、CMSは古い世代のヒープを最適化できず、最終的に長いフルGCを実行します。
  • G1では、他のコレクタよりも高いオーバーヘッドを示し、この同時性のためにスループットに影響を及ぼす場合があります。

動作方法のために、G1はガベージ・コレクションの効率性を向上させる固有のメカニズムを備えています。

  • G1では、コレクション時に古い世代の大きな空の領域を完全に回収できます。これは、多くの不要なガベージ・コレクションを回避して、大量の領域を容易に解放できます。
  • オプションで、G1は同時にJavaヒープで文字列の重複を除外できます。

古い世代からの大きな空のオブジェクトの回収は、常に有効になっています。オプション-XX:-G1EagerReclaimHumongousObjectsを指定すると、この機能を無効化できます。デフォルトで、文字列の重複除外は無効になっています。オプション -XX:+G1EnableStringDeduplicationを使用すると、これを有効化できます。