原文はこちら。
The original article was written by Thomas Schatzl ().
https://tschatzl.github.io/2025/08/12/jdk25-g1-serial-parallel-gc-changes.html
JDK 25 RC 1 が公開されました。いつも通り、今回のリリースにおけるOpenJDKのStop-the-world GCの変更点を簡単に説明します。
JDK 25: First Release Candidate
https://mail.openjdk.org/pipermail/jdk-dev/2025-August/010296.html
JDK 25向けHotspot GCサブコンポーネント全体の変更点一覧はこちらから確認できます。解決またはクローズされた変更点は合計約200件で、最近の傾向と一致しています。
JDK 25 の興味深い変更点から見ていきましょう。
Parallel GC
[JDK-8192647] GClocker induced GCs can starve threads requiring memory leading to OOME
Parallel(およびSerial)GCにおける最も重要な変更はJDK-8192647でしょう。
アプリケーションでJNIを多用している場合、Retried Waiting for GCLocker Too Often Allocating XXX wordsという警告の後、VMが予期せずシャットダウンする現象が発生した状況に遭遇したかもしれません。今回のリリース以降、どちらのGCでもこの現象は発生しなくなりました。
Java Native Interface Specification: Contents
https://docs.oracle.com/en/java/javase/24/docs/specs/jni/
この修正により、JNIによりGC実行がブロックされたスレッドは、放棄する前に必ず自身のGCをトリガーすることが保証されます。
Serial GC
Parallel GCで説明したのと同じGCLockerの問題が修正されました。
[JDK-8346920] Serial: Support allocation in old generation when heap is almost full
JDK-8346920では、特定の状況でfull GCを回避する問題が修正されています。
G1 GC
[JDK-8343782] G1: Use one G1CardSet instance for multiple old gen regions
JDK-8343782により、G1は任意のOld generation領域のremembered set (記憶集合) を他の領域とマージできるようになり、前回リリースと比較してより一層のメモリ節約が可能になりました。この変更に関するPRでいくつかのグラフを確認できます。
Remembered Set
https://docs.oracle.com/en/java/javase/24/gctuning/garbage-first-g1-garbage-collector1.html#GUID-99526C47-2C71-408C-9DBE-4F38ED839FF0
https://docs.oracle.com/javase/jp/24/gctuning/garbage-first-g1-garbage-collector1.html#GUID-99526C47-2C71-408C-9DBE-4F38ED839FF0
8343782: G1: Use one G1CardSet instance for multiple old gen regions #22015
https://github.com/openjdk/jdk/pull/22015
このPRから引用した以下のグラフは、remembered setを多用する特定のベンチマークにおいて、remembered setのメモリ使用量が大幅に減少したことを示しています。

赤線は64GB Javaヒープ環境における前述ベンチマークでのremembered setのネイティブメモリ使用量を示し、青線は本パッチ適用後の使用量です。VMの当該部分のネイティブメモリ消費量は、ピーク時2GBから約0.75GBに低下しました。
G1はold generationの領域回収フェーズ (Space-Reclamation phase) 終了時に発生するポーズ時間のスパイクを回避するため、新たな対策を導入しました。これは情報不足が原因で候補領域の選択が最適でなかったことが原因でした。
Garbage Collection Cycle
https://docs.oracle.com/en/java/javase/24/gctuning/garbage-first-g1-garbage-collector1.html#GUID-F1BE86FA-3EDC-4D4F-BDB4-4B044AD83180
https://docs.oracle.com/javase/jp/24/gctuning/garbage-first-g1-garbage-collector1.html#GUID-F1BE86FA-3EDC-4D4F-BDB4-4B044AD83180
現在の処理は概ね以下の通りです。
Remark停止中に、ヒープ全体の生存分析後、G1は後で回収する候補領域のセットを決定する。- G1は最も効率的な(つまりメモリの節約効果が大きく、回収の労力が少ない)領域を選ぼうとする。
- 次に、これらの候補領域について、
RemarkからCleanupのポーズ期間中に、G1はremembered set(当該領域内の生存オブジェクトへの参照を全て保持するデータ構造)を再計算する。- remembered setは退避対象オブジェクトへの参照の場所を保持しているため、後続のGC停止時にオブジェクトを待避させるのに必要である。
- 退避後、これらの参照は最終的に新しいオブジェクトの場所を指す必要がある。
候補領域の決定こそが、ポーズ時間長期化の原因です。退避効率は、領域内の生存データの量と、その領域のremembered setのサイズの加重合計で決まります。G1は、生存率が低くremembered setが小さい領域を優先的に退避させますが、これは最も速く退避できるからです。しかし候補領域を選定する時点では、G1はremembered setの情報を持ち合わせていません。実際、G1はこの選定直後に情報を再構築する計画だからです。これまでG1は生存率のみに依存し、remembered setのサイズは無関係と仮定してきました。
残念ながら、多くの場合これは正しくありません。退避は十分に速く、ポーズ時間の目標も余裕があるため、確かに大半のケースでは問題にならないのですが、後に行われるmixed collection (混合コレクション) は遅くなりがちだと気づいたかもしれません。最悪の場合、この挙動は領域回収フェーズの最終のmixed collectionで大きなポーズ時間の急増を引き起こします。これは、退避コストが最も高い領域のみが残されているためです。
この問題に対する一つの選択肢は、単にこれらの領域の退避をスキップする、というものです。しかし問題は、最初の候補選択が、領域回収フェーズ中に十分なスペースを解放し、full heap compaction (ヒープの完全圧縮) による中断なしに継続できるように設計されていた点にあります。したがって、領域回収から継続的に除外し続けると、full heap compactionが発生する可能性があります。
JDK-8351405での変更では、代わりに選択メカニズムを改善します。
[JDK-8351405] G1: Collection set early pruning causes suboptimal region selection
https://bugs.openjdk.org/browse/JDK-8351405
その時点でのremembered setのサイズの正確なメトリックは持たないものの、代用となり得る安価で高速な計算方法は存在しないでしょうか?実は存在します。マーキング中に、G1は各領域で遭遇する流入参照の数を現在カウントしています(単にカウントするだけで保存はしていません)。remembered setはこれらの位置情報を保存していますが、この代替手段はより粗い粒度で管理しているという点で、非常に優れた代替手段です。さらに、GCポーズ中に必要な作業量は、結局のところ参照数の多さに比例します。
G1はこの精度が高い効率情報を、Remarkポーズ中の候補選択時に活用します。これにより、ポーズ時間の急増を回避できます。
PR #24076から引用した下図は、この変更がポーズ時間におけるこの変更の影響を示したものです。
8351405: G1: Collection set early pruning causes suboptimal region selection #24076
https://github.com/openjdk/jdk/pull/24076

JDK 25(茶色)、JDK 24(紫色)、変更後のJDK 25(緑色)のmixed GCポーズ時間を、デフォルトの停止時間目標200msで示しています。最初の2つのグラフは、領域回収フェーズにおける最後のmixed GCが常に非常に長いことを示しています(このスパイクが最後のものであることは、これらのmixed GC間の時間間隔が長いことから推測できます。その間には、ここに表示されていないYoung generationのみのGCによるポーズがいくつか存在します)。ご覧の通り、この手法により最大400msにも及ぶこれらのスパイクが完全に解消されていることがわかります。
「New Write Barriers for G1」で説明したwrite-barrier(書き込みバリア)の改善は、当初JDK 25に実装予定でしたが、プロセス上の問題により間に合いませんでした。JDK 26での実装が期待されます。
New Write Barriers for G1
https://tschatzl.github.io/2025/02/21/new-write-barriers.html
https://logico-jp.dev/2025/03/22/new-write-barriers-for-g1/
[JDK-8340827] JEP 522: G1 GC: Improve Throughput by Reducing Synchronization
https://bugs.openjdk.org/browse/JDK-8340827
一部のエッジケースにおける不要なGC停止を回避する修正が実施されました。
[JDK-8355756] G1HeapSizingPolicy::full_collection_resize_amount should consider allocation size
https://bugs.openjdk.org/browse/JDK-8355756
[JDK-8355681] G1HeapRegionManager::find_contiguous_allow_expand ignores free regions when checking regions available for allocation
https://bugs.openjdk.org/browse/JDK-8355681
別の変更により、特定のガベージコレクションのパフォーマンスが向上しています。
[JDK-8271871] G1 does not try to deduplicate objects that failed evacuation
https://bugs.openjdk.org/browse/JDK-8271871
【2025/09/03更新】JDK 25 リリースに間に合いませんでしたが、G1 が Transparent Huge Pages (THP) による大容量ページ取得に失敗する問題を確認しました。これはmadvise設定で THP を使用する全アプリケーションでパフォーマンス低下の原因となる可能性があります。次期更新リリースで修正予定です。
[JDK-8366434] THP not working properly with G1 after JDK-8345655
https://bugs.openjdk.org/browse/JDK-8366434
[JDK-8366564] Release Note: -XX:+UseTransparentHugePages Fails to Enable Huge Pages for G1
https://bugs.openjdk.org/browse/JDK-8366564
What’s next
現在G1界隈では、JDK-8359211 (Automatic Heap Sizing for G1) の実装に注力しています。これは環境の空きメモリを追跡し、それに応じてヒープサイズを調整することで、効率的で利用可能なメモリ予算を浪費しない最大JavaヒープサイズをG1が自動決定するものです。
[JDK-8359211] Automatic Heap Sizing for G1
https://bugs.openjdk.org/browse/JDK-8359211
自動ヒープサイジングに必要な変更(前述の不要なGCポーズを回避する修正に加え、JDK-8247843など)の一部は既にJDK 26に実装済みであり、いくつかの準備段階の修正(例:JDK-8359348やJDK-8357445)がレビュー待ちの状態です。
[JDK-8247843] Reconsider G1 default GCTimeRatio value
https://bugs.openjdk.org/browse/JDK-8247843
[JDK-8359348] G1: Improve cpu usage measurements for heap sizing
https://bugs.openjdk.org/browse/JDK-8359348
[JDK-8357445] G1: Time-Based Heap Uncommit During Idle Periods
https://bugs.openjdk.org/browse/JDK-8357445
このリリース期間内に完了させたい別のJEPは、Ergonomicsが常にG1をデフォルトGCとして選択するようにし、この特定のトピックに関する混乱をこれ以上生じさせないようにすることです。
[JDK-8359802] Ergonomics Always Select the G1 Garbage Collector as Default
https://bugs.openjdk.org/browse/JDK-8359802
Thanks go to…
いつものように、またしても素晴らしいJDKリリースに貢献してくださった皆様へ。次回(さらに素晴らしい)のリリースでお会いできるのを楽しみにしています。