
ガベージコレクタの進化とZGCの登場
Javaアプリケーションのパフォーマンスを考える上で、ガベージコレクタ(GC)は重要な要素です。
特に、大規模なデータ処理やリアルタイム性が求められるシステムでは、GCの一時停止(Stop-The-World: STW)がアプリケーションの応答性に大きな影響を与えます。
ZGC(Z Garbage Collector)は、このSTW時間を極限まで短縮することを目指してJava 11で導入された低レイテンシGCです。
Java 21で本番利用可能となった「Generational ZGC (JEP 439)」は、ZGCに世代別GCの概念を取り入れることでスループットの課題にも対応し、低レイテンシと高スループットの両立を目指しています。
さらに、Java 23ではこの世代別モードがデフォルトとなり、Java 24では従来の非世代別モードが廃止されるなど、Generational ZGCは現代Javaの標準的なGCとしての地位を確立しつつあります。
本記事では、Generational ZGCの仕組みから最新動向、他のGCとの比較、そしてSpring Bootでの実践的な設定方法までを解説します。
目次
- 1. Generational ZGCを理解するための基本コンセプト
- 1-1. GCの基本と「応答速度」の問題
- 1-2. “止めない”GCへの挑戦 – ZGCの登場
- 1-3. GCの効率を上げる工夫 – 世代別GC
- 2. Generational ZGCとは?:世代別GCとZGCの融合
- 2-1. Generational ZGCのメリット
- 3. Generational ZGCの有効化とJVMオプション
- 3-1. 主要なJVMオプション
- 4. パフォーマンス測定とチューニングのヒント
- 4-1. Java Flight Recorder (JFR) と Java Mission Control (JMC)
- 4-2. GCログの活用
- 5. 他の主要GCとの比較 (G1GC, Shenandoah)
- 6. 実践的ユースケース:Spring Bootとコンテナ環境
- 6-1. Spring Boot 3/4での設定例
- 7. Java 25の改善と将来の展望
- 7-1. Java 25での内部改善
- 7-2. Project Loom, Valhallaとの連携
- まとめ:Javaパフォーマンスの新たな標準へ
対象読者
- Javaアプリケーションのパフォーマンス・チューニングに関心のある開発者
- システムのレイテンシやスループットの改善を目指すSRE、インフラエンジニア
- 最新のJava GC技術(ZGC, Generational ZGC)について深く学びたい方
- Spring Bootやクラウドネイティブ環境でのJava実行環境の最適化を検討している方
(※本記事は、Javaのガベージコレクションやメモリ管理に関する基本的な知識があることを前提としています。)
1. Generational ZGCを理解するための基本コンセプト
本題に入る前に、Generational ZGCの背景にある3つの基本的なコンセプトを解説します。これらを把握することで、この技術の目的と利点がより明確になります。
1-1. GCの基本と「応答速度」の問題
ガベージコレクション(GC)とは?
Javaの便利な特徴の一つに「自動メモリ管理」があります。プログラムが新しいデータ(オブジェクト)を作成するとメモリが消費されますが、そのデータが不要になった後、手動でメモリを解放するのは大変で、ミスも起こりがちです。
この仕組みがガベージコレクション(GC)、通称「お掃除屋さん」です。GCは、プログラム内で使われなくなったメモリ(ガベージ)を自動的に探し出して解放し、メモリ不足を防いでくれます。
「Stop-The-World (STW)」とは?
しかし、このお掃除には一つ大きな課題があります。GCが最も安全かつ確実に掃除を行うためには、アプリケーションのすべての動作を一時的に停止させる必要があります。これを「Stop-The-World(STW)」と呼びます。
STWは、「お掃除の時間です!全員、一旦手を止めてください!」と号令がかかる状態に似ています。この停止時間(ポーズ)が長引くと、ユーザーからはアプリケーションがフリーズしたように見え、応答性が著しく悪化します。これが「応答速度」の問題です。
パフォーマンスの重要指標:レイテンシとスループット
この問題を理解するために、2つの重要なパフォーマンス指標を見ていきましょう。
- レイテンシ(Latency)
- 意味: 応答速度。リクエストを送ってから応答が返ってくるまでの時間。
- GCとの関係: STWによる停止時間が長いほど、レイテンシは悪化(時間が長く)します。Webサイトのボタンをクリックしてもなかなか画面が変わらない、といった現象を引き起こします。
- スループット(Throughput)
- 意味: 処理能力。単位時間あたりにどれだけ多くの作業をこなせるか。
- GCとの関係: GCの処理自体にもCPUリソースが使われます。GCの効率が悪く、頻繁に重い処理が走ると、アプリケーション本来の処理に使えるリソースが減り、スループットは低下します。
1-2. “止めない”GCへの挑戦 – ZGCの登場
このSTWによる応答速度の問題を解決するために開発されたのがZGC(Z Garbage Collector)です。
ZGCの最大の目標は、STWの時間を極限まで短縮すること。その目標は、ヒープサイズがどれだけ巨大(数TB!)になっても、停止時間を1ミリ秒未満に抑えるという、非常に高い目標です。
この目標を達成する中心的な仕組みが「コンカレント(Concurrent)処理」です。ZGCは、「カラーポインタ」や「ロードバリア」といった高度な技術を用いて、アプリケーションが動作している横で、GCのほとんどの作業(マーキングやオブジェクトの移動など)を並行して実行します。
これにより、ZGCは「低レイテンシ」を最優先事項として達成しています。
1-3. GCの効率を上げる工夫 – 世代別GC
GCの効率を向上させるためのもう一つの仕組みが「世代別GC」です。これは、ほとんどのアプリケーションにおいて観測される、ある経験則に基づいています。
世代仮説 (Generational Hypothesis)
- ほとんどのオブジェクトは、作成後すぐに不要になる(若くして死ぬ)。
- GCを何度も生き残ったオブジェクトは、その後も長く使われ続ける傾向がある。
この仮説に基づき、「世代別GC」はメモリ領域を大きく2つに分割して管理します。
- Young世代(若い世代):
- 新しく作られたオブジェクトが置かれる場所。ほとんどのオブジェクトはすぐに不要になるため、ここで頻繁に、しかし高速なGC(マイナーGC)を実行します。
- Old世代(古い世代):
- Young世代のGCを何度も生き残り、「長生きする」と判断されたオブジェクトが移動してくる場所。ここのGC(メジャーGC)は、Young世代ほど頻繁には実行されません。
短命なオブジェクトで溢れるYoung世代にGCの労力を集中させることで、GCプロセス全体の効率が大幅に向上します。この結果、アプリケーション本体の処理に多くのCPU時間を割り当てることができ、高スループットの向上に繋がります。
以上の背景から、Generational ZGCは、ZGCの「低レイテンシ」と、世代別GCの「高スループット」という特徴を併せ持つように設計されています。
2. Generational ZGCとは?:世代別GCとZGCの融合
Generational ZGCは、その名の通り、世代別ガベージコレクションの考え方をZGCに組み込んだものです。ヒープを「Young世代」と「Old世代」に分割し、オブジェクトの生存期間に応じて効率的にGCを行います。
- Young世代:
- 新しく作成されたオブジェクトが配置される領域。ほとんどのオブジェクトは短命であるという「世代仮説」に基づき、Young世代でのGCを頻繁に行うことで、効率的に不要なオブジェクトを回収します。
- Old世代:
- Young世代でのGCを生き残った長命なオブジェクトが移動する領域。Old世代でのGCはYoung世代に比べて頻度が低くなります。
ZGCの核となる技術「カラーポインタ」を使い、アプリケーションスレッドをほぼ停止させることなくオブジェクトの移動(コンカレントリロケーション)を実現しつつ、世代別GCを導入することで、以下のメリットがあります。
2-1. Generational ZGCのメリット
- スループットの大幅な向上:
- 世代仮説に基づき、GCの対象を短命なオブジェクトが多いYoung世代に集中させることで、GCの効率が大幅に向上します。これにより、CPUサイクルをより多くアプリケーション処理に割り当てられるようになり、一部のベンチマークでは、非世代別ZGCと比較して最大4倍のスループット向上が報告されています。
- 1ミリ秒未満の低レイテンシの維持:
- ZGCの最大の特徴である低STW時間はそのままに、世代別GCのメリットを享受できます。これにより、アプリケーションはGCによる一時停止をほとんど意識することなく、高い応答性を維持できます。
- ヒープサイズに依存しない効率的なGC:
- Young世代のGCはヒープサイズ全体に依存せず効率的に行われるため、アロケーションストール(メモリ確保の遅延)のリスクが大幅に低減され、非常に大きなヒープを持つアプリケーションでも安定したパフォーマンスを発揮しやすくなります。
3. Generational ZGCの有効化とJVMオプション
Generational ZGCの有効化方法はJavaのバージョンによって異なります。
- Java 21, 22: 以下の両方のオプションが必要です。
bash java -XX:+UseZGC -XX:+ZGenerational YourApplication - Java 23以降: Generationalモードがデフォルトになったため、
-XX:+UseZGCだけで有効になります。bash java -XX:+UseZGC YourApplication
このバージョンで-XX:+ZGenerationalを指定すると、非推奨である旨の警告が表示されます。
Java 24で非世代別モードは廃止されたため、-XX:-ZGenerational オプションは利用できません。
3-1. 主要なJVMオプション
-Xms<size>/-Xmx<size>: ヒープの初期サイズと最大サイズ。ZGCは動的にヒープを調整しますが、適切な初期値は重要です。-XX:MaxRAMPercentage=<percent>: コンテナ環境で推奨。コンテナに割り当てられたメモリに対するヒープの最大割合を指定します。-Xmxの代わりに利用することで、より柔軟なメモリ管理が可能です。-XX:+AlwaysPreTouch: 起動時にJVMがヒープ全体を物理メモリにマッピングしておくことで、実行中のページフォールトによる遅延を防ぎます。低レイテンシを最優先する場合に有効です。-XX:ZCollectionInterval=<seconds>: 強制的にGCを実行する最小間隔を秒単位で指定します。-XX:ZUncommitDelay=<seconds>: 未使用のメモリをOSに解放するまでの遅延時間を指定します。-Xlog:gc*:file=gc.log: GCの動作を詳細にログファイルに出力します。パフォーマンス分析とチューニングには不可欠です。
4. パフォーマンス測定とチューニングのヒント
4-1. Java Flight Recorder (JFR) と Java Mission Control (JMC)
JFRとJMCは、GCの活動、ヒープ使用状況、STW時間などを詳細に分析するための強力なツールです。Generational ZGCの導入効果を測定する上で非常に有効です。
4-2. GCログの活用
-Xlog:gc* で出力されるGCログは、パフォーマンス問題を診断するための基本的な情報源です。GCの停止時間、各フェーズの所要時間、メモリの解放量などを分析することで、チューニングの方向性を判断できます。
5. 他の主要GCとの比較 (G1GC, Shenandoah)
Generational ZGCは有力な選択肢ですが、常に唯一のGCとは限りません。アプリケーションの要件に応じて最適なGCを選択することが重要です。
| 特徴 | Generational ZGC | G1GC (Garbage-First) | Shenandoah GC |
|---|---|---|---|
| 主なターゲット | 低レイテンシ、高スループット、大ヒープ | スループットとレイテンシのバランス | 低レイテンシ、G1GCより短い停止時間 |
| 最大停止時間 | 1ms未満 | 数十~数百ms(調整可能) | 10ms未満(目標) |
| スループット | 非常に高い | 高い | 比較的高い |
| メモリ効率 | 世代別化で大幅に改善 | 効率的 | 比較的効率的 |
| 適したヒープサイズ | 数GB~数十TB | 数GB~ | 数GB~ |
| 適したユースケース | APIサーバー、金融取引、リアルタイム分析 | 多くの一般的なWebアプリ、バッチ処理 | マイクロサービス、UIを持つアプリ |
| デフォルトGC | (Java 23以降 ZGCのデフォルト) | Java 9以降 | – |
6. 実践的ユースケース:Spring Bootとコンテナ環境
Generational ZGCは、特にSpring Bootで構築されたマイクロサービスや、Docker/Kubernetes上で動作するクラウドネイティブアプリケーションで特に有効です。
6-1. Spring Boot 3/4での設定例
Spring BootアプリケーションでGenerational ZGCを有効にするには、Dockerfile または起動スクリプトでJVM引数を渡すのが一般的です。
Dockerfileの例 (Java 23以降)
FROM eclipse-temurin:23-jre-alpine
WORKDIR /app
COPY target/my-app.jar .
# コンテナのメモリの80%をヒープとして使用し、ZGCを有効化
ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -XX:+UseZGC"
ENTRYPOINT ["java", "-jar", "my-app.jar"]このように -XX:MaxRAMPercentage を使うことで、コンテナに割り当てられたメモリ量に応じて動的にヒープサイズが決定され、効率的なリソース利用が可能になります。
7. Java 25の改善と将来の展望
Generational ZGCはJava 21の登場で完成したわけではなく、その後も継続的に改善されています。
7-1. Java 25での内部改善
Java 25では、特定のJEPとしてではないものの、以下のような重要な内部改善が行われました。
- ページ割り当ての最適化: メモリ管理がより効率化され、ヒープの断片化がさらに抑制されました。
- 仮想アドレス空間の管理改善: 特に巨大なヒープを持つアプリケーションでの安定性が向上しました。
7-2. Project Loom, Valhallaとの連携
Project Loomによって導入された仮想スレッドは、大量のスレッドを効率的に扱えるようにしますが、これはGCにとっても新たな挑戦です。Generational ZGCの短い停止時間と高いスループットは、多数の仮想スレッドが同時に動作する環境でも、アプリケーション全体の応答性を高く維持するために不可欠です。
また、Project Valhallaで導入されるValue Objectは、メモリレイアウトを最適化し、GCの負荷を軽減する可能性があります。Generational ZGCは、これらの新しいJavaの機能と連携し、将来のアプリケーションパフォーマンスをさらに向上させる重要な役割を担います。
まとめ:Javaパフォーマンスの新たな標準へ
Java 21で登場し、Java 23でデフォルトとなったGenerational ZGCは、ZGCが持つ低レイテンシの特性に世代別GCの効率性を組み合わせることで、Javaアプリケーションのパフォーマンスを大きく向上させます。
特に、高スループットと低レイテンシが同時に求められる現代の大規模システムにおいて、Generational ZGCは有力な選択肢となります。
この新しいGCを理解し、適切に設定・チューニングすることで、Javaアプリケーションのより高速で安定した動作に繋がり、ユーザー体験が向上します。
もはや特別な選択肢ではなく、現代のJava開発におけるパフォーマンスの標準的な選択肢として、ぜひGenerational ZGCの導入を検討してください。
免責事項
- 本記事に記載されている情報は、記事公開時点(Java 25リリース前後)のものです。将来のJavaバージョンアップにより、仕様や推奨設定が変更される可能性があります。
- 記事内のサンプルコードや設定例は、特定の環境下での動作を想定しています。ご自身の環境で適用する際は、十分なテストと検証を実施してください。
- 本記事の情報に基づいて生じたいかなる損害についても、筆者は一切の責任を負いかねます。
