Red Hat で Java Platform Advocate として OpenJDK を担当している伊藤ちひろ(@chiroito)です。
この記事は、OpenJDK のWikiページ、https://wiki.openjdk.java.net/display/shenandoah/Main の一部を翻訳した記事です。何回かに分けて翻訳していきますので、ぜひ最後までごらん下さい。
翻訳の各リンクはこちらです。
- Shenandoah GCのサポート概要とバグ報告について - 赤帽エンジニアブログ
- Shenandoah GCの実装概要 - 赤帽エンジニアブログ
- Shenandoah GCの性能指針と診断 - 赤帽エンジニアブログ
- Shenandoah GCの機能診断 - 赤帽エンジニアブログ
実装の概要
Shenandoahは領域化されたコレクタであり、ヒープを領域の集合体として維持します。
通常のShenandoahのGCサイクルは、以下のようになります:
GC(3) Pause Init Mark 0.771ms GC(3) Concurrent marking 76480M->77212M(102400M) 633.213ms GC(3) Pause Final Mark 1.821ms GC(3) Concurrent cleanup 77224M->66592M(102400M) 3.112ms GC(3) Concurrent evacuation 66592M->75640M(102400M) 405.312ms GC(3) Pause Init Update Refs 0.084ms GC(3) Concurrent update references 75700M->76424M(102400M) 354.341ms GC(3) Pause Final Update Refs 0.409ms GC(3) Concurrent cleanup 76244M->56620M(102400M) 12.242ms
上記のフェーズでは、おおよそ以下のようなことをします:
Init Markは並行マーキングを開始します。ヒープとアプリケーションスレッドを並行マーク用に準備し、次にルートセットをスキャンします。これはサイクルの最初の停止であり、最も支配的な消費者はルートセットのスキャンです。したがって、その持続時間はルートセットサイズに依存します。
並行マーキングはヒープ上を走査し、到達可能なオブジェクトを追跡します。このフェーズはアプリケーションと並行して実行されます。そして、その時間は生きているオブジェクトの数とヒープ内のオブジェクトグラフの構造に依存します。このフェーズでは、アプリケーションは新しいデータを自由に割り当てられるため、並行マーキング中はヒープ占有率が上昇します。
Final Markは、保留中のマーキング/更新キューをすべて排出し、ルートセットを再スキャンすることで、並行マーキングを終了します。また、退避させる領域(コレクションセット)を決定し、いくつかのルートを事前に退避させ、一般的に次のフェーズのための処理を準備することによって、退避を初期化します。この作業の一部は、並行事前クリーニング・フェーズの間に同時に行われます。これはサイクルの2番目の停止で、ここで最も支配的な時間消費者は、キューを排出し、ルートセットをスキャンすることです。
並行クリーンアップは、即時ゴミ領域が再要求されます。この領域はつまり、並行マーキングの後に検出された生きているオブジェクトが存在しない領域です。
並行退避は、コレクションセットから他の領域にオブジェクトをコピーします。ここが他のOpenJDKのGCと大きく異なる点です。このフェーズは再びアプリケーションと一緒に実行されます。そして、アプリケーションは自由に割り当てられます。その期間は、そのサイクルで選択されたコレクションセットのサイズに依存します。
参照更新の初期化(Init-UR)は、参照元を更新するフェーズを初期化します。これは、すべてのGCとアプリケーションのスレッドで退避を完了したことを確認し、次のフェーズのためにGCを準備する以外はほとんど何もしません。これはサイクルの3番目の休止で、すべての中で最も短いものです。
並行更新参照はヒープ上を走査し、並行退避中に移動したオブジェクトへの参照を更新します。ここが他のOpenJDKのGCとの大きな違いです。その時間はヒープ内のオブジェクト数に依存しますが、オブジェクトグラフ構造には依存しません。なぜなら、ヒープを直線的にスキャンするためです。このフェーズは、アプリケーションと同時に実行されます。
最終参照更新(Filan-UR)は、既存のルートセットを再更新することで、参照更新フェーズを終了します。また、そのコレクションセットから領域を再利用します。なぜなら、現在ヒープには(古い)オブジェクトへの参照はないからです。これはサイクルの最後の停止です。その期間はルートセットのサイズに依存します。
並行クリーンアップでは、コレクションセット領域が再要求されます。これは現在ではその領域への参照はないためです。