JDK 21: The GCs keep getting better

原文はこちら。
The original article was written by Stefan Johansson (Software Engineer at Oracle).
https://kstefanj.github.io/2023/12/13/jdk-21-the-gcs-keep-getting-better.html

数年前、3つの主要なGCについて、JDK 8からJDK 17の間のGCの進歩についての記事を書きました。

GC progress from JDK 8 to JDK 17
https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html

この秋にJDK 21という新しいLTSリリースが出てきました。

The Arrival of Java 21!
https://inside.java/2023/09/19/the-arrival-of-java-21/
https://logico-jp.io/2023/09/20/the-arrival-of-java-21/

JDK 21でベンチマークし、GCパフォーマンス・チャートを作成しました。JDK 21とJDK 17以降の他のリリースでは、Virtual Threads、スイッチのパターン・マッチング、Generational ZGCなど、注目すべき機能が導入されています。それではパフォーマンスを見てみましょう。

JEP 444: Virtual Threads
https://openjdk.org/jeps/444
JEP 441: Pattern Matching for switch
https://openjdk.org/jeps/441
JEP 439: Generational ZGC
https://openjdk.org/jeps/439

Introduction

JDKリリース間でパフォーマンスを比較する場合、どの機能が特定のパフォーマンスを向上させたかを正確に言うのは難しいのですが、JDK 8以降、Javaプラットフォーム全体の性能が大幅に向上していることは容易にわかります。この記事では、性能向上を示すためにSPECjbb® 2015を使用します。

SPECjbb®2015[1]
https://spec.org/jbb2015/

これはよく知られた標準ベンチマークで、GCに加えられた変更を示すのに適しています。その主な理由は、ベンチマークが2つのスコアを提供する点にあります。

  • max-jOPS – 素のスループット
  • critical-jOPS – レイテンシ制約下でのスループット

GCの改善により両方のスコアが向上しますが、レイテンシ制約のあるスコアの改善は、GCに対して行われた変更により多く結びつきます。基本的に、ポーズ時間が短いほどスコアは向上します。素のスループット・スコアについては、JITやJavaプラットフォームの他の部分の改善も関わります。

あまりチューニングせずにベンチマークを実行していますが、ヒープサイズを16GBに固定し、ラージページを有効にして、ベンチマークを実行する前にページングされるようにしています。結果にはout-of-the-box behavior、つまり標準で使える挙動を反映させたいところですが、公平で一貫性のある結果を得るには、このような設定が良いと考えています。

Choosing your GC

Oracleは4種類のGCをサポートしており、それぞれ異なるユースケースに対応しています。このエントリではSerial GCを含めていませんが、それは使用するベンチマークに適していないためです。Serial GCの主な焦点は低オーバーヘッドであり、メモリとCPUリソースの量が限られているユースケースに適しています。この比較で取り上げるGCは以下の通りです。

G1JDK 9 以降のデフォルトGC
レイテンシと待ち時間とスループットのバランスに重点を置いている。
Parallelスループット重視のGC
Z超低レイテンシーに対応するGC
ミリ秒以下のポーズ時間で完全な並行処理を実現

どのGCを使用するかは、アプリケーションにとって最重要なものが何か次第です。それぞれのGCが最適な選択肢となるユースケースがあり、すべてのユースケースに最適なGCは存在しません。この辺りの詳細は以下のエントリをご覧ください。

GC progress from JDK 8 to JDK 17
https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html#serving-different-use-cases

The progress

JDK 8までさかのぼると、G1とParallelに施された改良の量は非常に膨大です。この2つのGCはあらゆる面で改善されています。ポーズ時間が短くなり、メモリ使用量が減り、スループットも以前より向上しています。ZGCは登場から間もないので、このエントリではZGCを世代別GC (generational GC) にすることによってもたらされた改善に主に焦点を当てます。

この記事のチャートは、それぞれのGCを個別に比較していますが、その主たる理由は、ヒープサイズの構成によって、結果がどちらか一方のGCに有利になるからです。こうすることで、最高のGCを決めようとはせず、すべてのGCの大きな進歩に焦点を当てることができると考えています。

比較対象はJDK 8、JDK 17、JDK 21のG1とParallelです。ZGCについては、JDK 17、JDK 21、JDK 21の世代別ZGCの3つのデータを選択しました。JDK 17はZGCが完全にサポートされた最初のLTSであるため、それ以上さかのぼるのはあまり意味がないと考えています。

Throughput

素のスループットパフォーマンスに関しては、JDK 17以降の向上はそれほど大きくありませんが、それでもわずかに向上しています。しかし、以下のグラフで注目すべき点が2つあります。まず、G1およびParallelに関して、JDK 8と最近のJDKとの大きな違いです。パフォーマンスの観点からは、JDK 8を残す意味はまったくありません。

Throughput

2番目に注目すべき点は、世代別ZGCを使用した場合に見られる10%の改善です。ZGCによる新しい世代サポートにより、GCのたびにヒープ全体を考慮する必要がなくなり、より効率的にメモリを再利用できるようになりました。その結果、GC作業に費やされるCPUリソースが減り、そのリソースをアプリケーションで使用できるようになり、パフォーマンスが向上しています。

Latency

レイテンシのスコアもほぼ同じ傾向にあります。G1とParallelはJDK 8とJDK 17の間で大きな改善が見られますが、最高の結果はやはりJDK 21です。JDK 8とJDK 17の間で7年以上の技術革新があり、JDK 17と21の間には2年しかありません。この短い期間では、これまでGCを順調に動作するようにしたという事実と併せて、このような大規模ベンチマークで大きなゲインを得るのは難しいものです。

Latency

ZGCに世代の概念が追加されたことで、世代別ZGCとレガシー・モードの間の変化が大きいことがわかります。このゲインのほとんどは、スループット・スコアが向上したことに由来していることに留意すべきです。ポーズ時間の長さは、2つのZGCモード間で大きな違いはなく、どちらも1ミリ秒を大きく下回っています。それでも、最悪の場合のレイテンシを見ると、世代別ZGCは旧来のZGCと比較してわずかに優れていることがわかります。

P99-pause

G1とParallelでは、ポーズ時間に大きな変化はありません。G1ではポーズ時間が長くなっています。より高いパーセンタイルのポーズ時間を見ると、数ミリ秒を短縮できていることがわかります。

Footprint

最後のグラフは、固定負荷でベンチマークを実行した場合のネイティブメモリオーバーヘッドのピーク値を比較したものです。この点から見ると、Parallelは非常に安定しており、これ以上最適化する必要はありません。一方、G1については、過去10年間に多くの非効率な部分を削減できました。JDK 20では、G1を2つのマーキングビットマップではなく、1つのマーキングビットマップだけで済むように変更しました。これによる節約は大きく、このベンチマークではG1が最もメモリ効率の高いGCになりました。

Footprint

世代別ZGCでは、このベンチマークでより良いレイテンシとスループットを得るために行ったトレードオフがはっきりとわかります。その代償として、ネイティブ・メモリの消費量が増加しています。世代サポートを効率的に実装するには、old世代からyoung世代へのポインタを追跡する必要があります。これはremenbered sets (記憶集合) と呼ばれ、メモリーを消費します。また、複数の世代を扱うときに必要な他のメタデータのために、さらにメモリが必要です。とはいえ、ほとんどの場合、レガシーZGCと比較すると、世代別ZGCの方が総メモリ消費量は少なくなっています。そのため、より小さなヒープを使用することで、追加のネイティブ・メモリ使用量を節約でき、全体的なパフォーマンスが向上する可能性が高いです。

Just upgrade and try out Generational ZGC

もうお分かりのように、JDK 21はJDK 8に比べてパフォーマンスが大幅に向上しています。もしまだJDK 8を使っているなら、アップグレードを検討すべきでしょう。アップグレードする際には、どのGCを使うかを再検討する絶好の機会です。もしJDK 21に移行するのであれば、世代別ZGCを試してみることをお勧めします。JDK21では、ZGCの世代別バージョンとレガシー・モードの両方が利用可能で、世代別ZGCを使用するには、両方のフラグを指定する必要があります。

-XX:+UseZGC -XX:+ZGenerational

レガシーモードは、最終的に廃止を目指しています。これをスムーズに行うために、世代別ZGCがレガシーZGCほどうまく機能していないユースケースについて、ご利用の皆様からのフィードバックを求めています。もしそうしたユースケースがあれば、以下のOpenJDKメーリングリストを使ってお知らせください。

hotspot-gc-use — Technical discussion of usage of the HotSpot garbage collectors
https://mail.openjdk.org/mailman/listinfo/hotspot-gc-use

OracleのJavaチームからの他の一般的なニュースや洞察については、必ずinside.javaをチェックしてください。

Inside.java
https://inside.java


SPEC® and the benchmark name SPECjbb® are registered trademarks of the Standard Performance Evaluation Corporation.
For more information about SPECjbb, see www.spec.org/jbb2015/ 

コメントを残す

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