JDK 24 G1/Parallel/Serial GC changes

原文はこちら。
The original article was written by Thomas Schatzl (OpenJDK developer, Oracle).
https://tschatzl.github.io/2025/04/01/jdk24-g1-serial-parallel-gc-changes.html

JDK 24が数週間前にリリースされました。

JDK 24
https://openjdk.java.net/projects/jdk/24/

本記事では、このリリースにおけるOpenJDKのstop-the-worldコレクターの変更点について簡単に説明します。当初は話すことがあまりないだろうと考えていたため、公開が遅れてしまいましたが、今にして思えば、それは間違いでした。

前回のリリースと同様、JDK 24はGCの分野では比較的目立たないリリースですが、特にJDK 25については、良いニュースがあります。この記事の最後に触れたいと思います。

JDK 23 G1/Parallel/Serial GC changes
https://tschatzl.github.io/2024/07/22/jdk23-g1-serial-parallel-gc-changes.html
https://logico-jp.dev/2024/07/29/jdk-23-g1-parallel-serial-gc-changes/

JDK 24のHotspot GCサブコンポーネント全体の変更点の一覧はこちらから確認できます。合計で約190件の変更が解決またはクローズされました。特にこれまでと変わったところはありません。

また、JavaOneカンファレンスが再び開催されました。

JavaOne 2025
https://dev.java/community/javaone-2025/

Parallel GC

JDK-8269870により、(通常非常にHotになる)退避ループにおける不要な同期がいくつか削除されました。これにより、状況によってはポーズ時間が短縮される可能性があります。

[JDK-8269870] PS: Membar in PSPromotionManager::copy_unmarked_to_survivor_space could be relaxed
https://bugs.openjdk.org/browse/JDK-8269870

Serial GC

Serial GCのコード・クリーンアップとリファクタリングを引き続き実施しています。

G1 GC

Improve slow startup due to predictor initialization

G1は、アプリケーションとVMの環境の両方に依存するガベージ・コレクションのさまざまな側面を予測して、VMのポーズ時間目標を達成しようとします。この予測は、例えば

  • メモリーのコピーに実際にどれだけのコストがかかるか
  • 通常、何件の参照を更新する必要があるか

などが挙げられます。G1は、VMが実行されるたびに、関連する予測因子(predictor)を再学習します。起動時には、予測因子用のサンプルがまだ存在しないため、G1はこれらの予測にいくつかの事前計算された値を使用します。理想的には、これらの値は定期的に更新および維持され、すべての環境に適切であるべきですが、ご想像の通り、これは不可能な作業です。これらの事前計算された値は、実際にはかなり以前に大型のSPARCマシン上で決定されたものであり、その後…忘れ去られました。

これらの値が不適切ゆえに、実際に問題があります。現在では、JVMが動作するほとんどのマシンが、その参照マシン(=大型のSPARCマシン)よりも高速なので、これらの値は非常に保守的であり、例えば、予想コストは実際よりもはるかに高くなるため、G1 GCのポーズ時間は、新たに測定された値がこれらの事前設定値を最終的に事実上上書きするまで、必要以上に短くなる傾向があります。一部の測定では、G1が現在の環境に適応するまでに、30回ほどの意味のあるガベージ・コレクションが必要です。

JDK-8343189での変更により、予測因子で使用する新しい実際の値の使用方法が変更されました。既存の事前計算された予測因子の値に追加するのではなく、最初の実際の値がこれらの事前計算された予測因子の値を直接上書きします。これにより、最初のガベージ・コレクションを除いて、これらの予測のための実際のサンプルを使用することになります。これにより、G1のアプリケーションおよび環境への適応が大幅に高速化されます。ただし、ここで1つの小さな問題が生じます。当初の非常に保守的な推定値には、最初の数回のガベージ・コレクションではG1が目標のポーズ時間を超過しないことがほぼ保証されるという利点がありました。このため、予測値の出力はかなり抑えられていました。そのため、新しいシステムでは、最初の数回のガベージ・コレクションでは、停止時間が当初の目標を上回る可能性が高くなります。

[JDK-8343189] [REDO] JDK-8295269 G1: Improve slow startup due to predictor initialization
https://bugs.openjdk.org/browse/JDK-8343189

Use one G1CardSet instance for all young regions

さらに、私たちはネイティブメモリの使用量を減らす取り組みも行っており、今回も記憶集合 (remembered sets) を対象にしています。

Remembered Set
https://docs.oracle.com/en/java/javase/24/gctuning/garbage-first-g1-garbage-collector1.html#GUID-99526C47-2C71-408C-9DBE-4F38ED839FF0

ガベージコレクション・チューニングガイドの該当セクションの説明の通り、G1は現在、Young世代全体の記憶集合を単一のユニットとして管理し、重複を排除することでメモリを節約しています。

[JDK-8336086] G1: Use one G1CardSet instance for all young regions
https://bugs.openjdk.org/browse/JDK-8336086

Young世代の記憶集合エントリを減らすことで、GCのポーズ時間も(ごく)わずかに短縮されます。

JDK 23のこの記事で、最後にいくつかの追加グラフとともにこの考え方を説明しています。

JDK 23 G1/Parallel/Serial GC changes
https://tschatzl.github.io/2024/07/22/jdk23-g1-serial-parallel-gc-changes.html#single-remsets-multiple-regions
https://logico-jp.dev/2024/07/29/jdk-23-g1-parallel-serial-gc-changes/#whats-next

What’s next

前回のこのセクションには、複数の領域で単一の記憶集合を使用するというアイデアを、任意の領域に適用する方法とその効果について多くの情報が含まれていました。 young世代の記憶集合のマージを拡張するJDK-8343782は、すでにJDK 25に統合されていますが、old世代の領域に対してこのアイデアを実装し、ネイティブメモリのさらなる節約を実現します。 すなわち、この変更により、old世代の領域の記憶集合がデフォルトでマージされます。

[JDK-8343782] G1: Use one G1CardSet instance for multiple old gen regions
https://bugs.openjdk.org/browse/JDK-8343782

JDK 25で予定されている変更は、おそらくアプリケーションとG1 GCの相互作用に関する過去最大の変更の1つでしょう。VM内のアプリケーションで参照書き込みの都度実行される、ガベージコレクターと同期するための小さなコードである書き込みバリア (write-barrier) は、アプリケーションのスループットに大きな影響を与えます。特にG1では、このライトバリアが完全に再設計されました。このライトバリアを大幅に削減することで、大幅な改善が可能になります。

[JDK-8253230] G1 20% slower than Parallel in JRuby rubykon benchmark
https://bugs.openjdk.org/browse/JDK-8253230

この変更を今後数か月のうちに統合できるよう、現在JEPの準備を進めているところです。

[JDK-8340827] G1: Improve Application Throughput with a More Efficient Write-Barrier
https://bugs.openjdk.org/browse/JDK-8340827

詳細については、実装CR、それぞれのPR、およびこのブログのこのトピックに関する詳細なブログ記事もご覧ください。

[JDK-8342382] Implementation of JEP G1: Improve Application Throughput with a More Efficient Write-Barrier
https://bugs.openjdk.org/browse/JDK-8342382
8342382: Implementation of JEP G1: Improve Application Throughput with a More Efficient Write-Barrier #23739
https://github.com/openjdk/jdk/pull/23739
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/

全体として、この変更により、最大10%のフリースループットの改善、より短いポーズ時間、より優れたコード生成(およびコードサイズの削減)が期待できます。ネイティブメモリの使用に関して若干のマイナスの影響がありますが、許容範囲内であると考えています。以下のグラフで説明します。

Native Memory Usage BigRAMTester 20GB

上のグラフは、BigRAMTesterベンチマークにおけるG1 GCのネイティブメモリ使用量を示しています。20GBのJavaヒープを使用し、比較的新しいJDK(参考までに、JDK 8は8GBのネイティブメモリを使用)を対象としています。JDK 17からJDK 21への移行時に基本メモリ使用量が1.2GBから約800MBに減少しているのは、以下の記事で説明した変更によるものです。

Concurrent Marking in G1
https://tschatzl.github.io/2022/08/04/concurrent-marking.html
https://logico-jp.dev/2022/09/17/concurrent-marking-in-g1/

JDK 24の線(オレンジ)では、前述のyoung世代全体で単一の記憶集合を使用すること(とJDK-8225409)の影響を示しています。

[JDK-8225409] G1: Remove the Hot Card Cache
https://bugs.openjdk.org/browse/JDK-8225409

最後に、JDK 25(赤の破線、latest)では、JDK-8343782、old世代の記憶集合のマージ、および書き込みバリアの変更により、さらなるメモリの削減が可能になりました。では、どこにregressionがあるのでしょうか。JDK 25の線が最も良いように見えますが? 実際には、これらの変更により、以前よりも大きな静的メモリブロックが1つ必要になっています(固定のJavaヒープサイズの最大0.2%)。その影響はグラフの一番初めに現れています。このベンチマークでは、記憶集合による節約がこの新しいデータ構造の追加による節約を上回っていますが、一般的にはそうではありません。

このマイルストーンは完了し、JEPプロセスに沿ってこの変更を導入するのに加えて、最終的にリリースに統合する予定ですが、変更を控えめにしたため、この分野での作業は継続中です。今後、スループットがさらに改善される可能性もあります。

それ以外にも、現在hotspot-gc-devメーリングリストで議論されている興味深いトピックがいくつかあります。

長期的には、ZGCと同様の自動ヒープサイズ調整機能を、いわゆるstop-the-world方式のGCにも導入するプロジェクトが進行中です。MicrosoftとGoogleによるこの取り組みへの貢献に感謝します。

JEP draft: Automatic Heap Sizing for ZGC
https://openjdk.org/jeps/8329758

G1を真のデフォルトGCにするための公開ディスカッションも行われています。現時点では、VMのエルゴノミクス上、コマンドラインでGCを指定しない場合、小さなヒープと少ないCPUリソースの環境では、G1ではなくSerial GCが選択される可能性があります。

RFC: G1 as default collector (for real this time)
https://mail.openjdk.org/pipermail/hotspot-gc-dev/2025-February/051121.html
[EXTERNAL] Re: RFC: G1 as default collector (for real this time)
https://mail.openjdk.org/pipermail/hotspot-gc-dev/2025-March/051557.html
[EXTERNAL] Re: RFC: G1 as default collector (for real this time)
https://mail.openjdk.org/pipermail/hotspot-gc-dev/2025-April/051778.html

ますます多くの測定結果で、追加のチューニングを行わずにデフォルト設定で実行した場合、ほとんどの、あるいはすべての評価基準において、G1がSerial GCに非常に近い、あるいはそれを上回ることが示されています。

もし後者のトピックについて興味深いデータをお持ちの方がいらっしゃいましたら、ぜひお知らせください。

Thanks go to…

いつも通り、JDK 24のリリースに貢献してくださった皆様に感謝いたします。次の(さらに素晴らしい)リリースでお会いできることを楽しみにしています。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください