JavaのGC:ZGCとは?
はじめに
はじめまして、三菱UFJインフォメーションテクノロジーの田中です。
最近、Javaアプリケーションのパフォーマンス計測に関わる機会があり、特にガベージコレクション(GC)の重要性を再認識しました。GCはJavaのパフォーマンスに大きな影響を与えるため、最新のGCアルゴリズムについて調べることにしました。
本記事では、Javaの歴史を振り返りながら、各バージョンで導入されたGCアルゴリズムについて詳しく解説します。
なお、調査したJavaはSun/Oracleのディストリビューションをベースに行いました。
Javaの歴史
Javaの各バージョンの概要とGCのアルゴリズムをまとめてみました。
凡例: ◎ デフォルトGC / □ 実験的機能 / ○ 一般使用可能 / △ 非推奨 / × 削除
Javaバージョン | リリース年 | LTS | 概要 | Mark-Sweep GC | Serial GC | Parallel GC | CMS GC | G1GC(Garbage First) | ZGC | Shenandoah GC | Epsilon GC |
---|---|---|---|---|---|---|---|---|---|---|---|
1.0 | 1996 | Javaの最初のリリース。基本的なクラスライブラリとJVM導入 | ○ | ||||||||
1.1 | 1997 | クラスローディングとイベントモデルの改善 | ○ | ||||||||
1.2 | 1998 | Java 2 プラットフォーム導入、Swing GUI、コレクションAPI追加 | ×(Serial GCへ置き換え) | ◎ | |||||||
1.3 | 2000 | ホットスポットVMの統合、JNDIサポート | ◎ | ||||||||
1.4 | 2002 | 例外チェーン、NIO(非同期I/O)、正規表現API追加 | ◎ | ○ | ○ | ||||||
5 | 2004 | ジェネリクス、アノテーション、可変長引数、列挙型追加 | ◎ | ○ | ○ | ||||||
6 | 2006 | JITコンパイラの強化、スクリプト言語のサポート、Oracle による Sun 買収 | ◎ | ○ | ○ | □ | |||||
7 | 2011 | try-with-resources、ダイヤモンド演算子、NIO2 | ○ | ◎ | ○ | ○ | |||||
8 | 2014 | ○ | ラムダ式、ストリームAPI、デフォルトメソッド導入 | ○ | ◎ | ○ | ○ | ||||
9 | 2017 | JPMS(モジュールシステム)、JShell導入 | ○ | ○ | △ | ◎ | |||||
10 | 2018 |
var 型導入、G1 GC の最適化 |
○ | ○ | △ | ◎ | |||||
11 | 2018 | ○ | 長期サポート(LTS)、HTTP クライアント標準化 | ○ | ○ | △ | ◎ | □ | □ | □ | |
12 | 2019 | Switch式の拡張、G1 GC の改善 | ○ | ○ | △ | ◎ | □ | □ | □ | ||
13 | 2019 | テキストブロックのプレビュー、メモリ管理の改善 | ○ | ○ | △ | ◎ | □ | □ | □ | ||
14 | 2020 | Recordsのプレビュー、NullPointerException の詳細化 | ○ | ○ | × | ◎ | □ | □ | □ | ||
15 | 2020 | Sealed クラス、Hidden クラスの導入 | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
16 | 2021 | パターンマッチングの拡張、Unixドメインソケット追加 | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
17 | 2021 | ○ | 長期サポート(LTS)、強化されたセキュリティ | ○ | ○ | - | ◎ | ○ | ○ | ○ | |
18 | 2022 | UTF-8 を標準文字セットに、Simple Web Server追加 | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
19 | 2022 | 仮想スレッド(Project Loom)プレビュー | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
20 | 2023 | スレッドAPIの改善、パターンマッチング強化 | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
21 | 2023 | ○ | 長期サポート(LTS)、仮想スレッド正式化 | ○ | ○ | - | ◎ | ○ | ○ | ○ | |
22 | 2024 | パターンマッチングの拡張、スレッドAPIの強化 | ○ | ○ | - | ◎ | ○ | ○ | ○ | ||
23 | 2024 | Project Loomの進化、Javaクラスライブラリの最適化 | ○ | ○ | - | ◎ | ○ | ○ | ○ |
GCの特徴
各GCアルゴリズムの特徴を整理してみます。
GCアルゴリズム | 特徴 | 向いているシステム | 向いていないシステム |
---|---|---|---|
Serial GC | 単一スレッドで動作し、メモリオーバーヘッドが低い | 小規模アプリケーション | 大規模アプリケーション、リアルタイム処理 |
Parallel GC | マルチスレッドで並列GCを実行し、スループットを重視 | バッチ処理、高スループットシステム | レイテンシの影響を受けやすいシステム |
CMS GC | Old世代GCを並行処理し、低レイテンシを実現 | 低レイテンシが求められるシステム | メモリ断片化が発生しやすい環境 |
G1GC(Garbage First) | スループットと低レイテンシのバランスが取れる | スループットとレイテンシのバランスが必要なシステム | 超低レイテンシが求められるシステム |
ZGC | 低レイテンシ、大規模ヒープに最適、複雑なメモリレイアウトに対応 | 大規模メモリを扱うリアルタイムシステム | 小規模システム |
Shenandoah GC | Red Hat社製、並行GCを実行し、ヒープサイズに関係なく低停止時間を維持(Red Hat社製) | 低レイテンシが求められるシステム | 高スループットが必要な環境 |
Epsilon GC | JVM開発・テスト向け、GCなし(メモリ枯渇時にクラッシュ)、CPU負荷ほぼゼロ | 開発・検証用 | 本番環境 |
ZGC(Z Garbage Collector)
私の知識では、Javaバージョン11頃で止まっていました。
GCアルゴリズムは、CMS GCが廃止されていたり、Shenandoah GCとZGCが増えていました。
今回は、直近のJavaリリース(バージョン23、24)で対応が入ったZGCを調べてみようと思います。
(余談ですが、ZGCのZに意味はないようです)
まず、ZGCに関連するJEPは下表の通りです。
Javaバージョン | JEP(番号、タイトル、リンク) | 概要 |
---|---|---|
11 | JEP 333:ZGC: A Scalable Low-Latency Garbage Collector (Experimental) | (実験的機能)Linux/x64へ追加 |
13 | JEP 351:ZGC: Uncommit Unused Memory (Experimental) | (実験的機能)未使用のヒープメモリをOSへ返却 |
14 | JEP 364:ZGC on macOS (Experimental) | (実験的機能)macOSへの移植 |
14 | JEP 365:ZGC on Windows (Experimental) | (実験的機能)Windowsへの移植 |
15 | JEP 377:ZGC: A Scalable Low-Latency Garbage Collector (Production) | 正式リリース |
21 | JEP 439:Generational ZGC | 世代ZGC追加 |
23 | JEP 474:ZGC: Generational Mode by Default | ZGCのデフォルトを世代モードへ(Java自体のデフォルトはG1GC) |
24 | JEP 490:ZGC: Remove the Non-Generational Mode | ZGCの非世代モードの削除 |
Java 23のリリースで、ZGCの「世代モード(Generational Mode)」がデフォルトになりました。
ZGCはもともと低レイテンシを特徴としていましたが、世代管理が追加され、さらに効率的なメモリ管理が可能になりました。
また、2025/3/18にリリース予定のJava 24では、非世代モードが削除され、世代モードのみサポート対象になります。
非世代モード(Non-Generational Mode)と世代モード(Generational Mode)の違い
非世代モード(Non-Generational Mode)
すべてのオブジェクトを同等に扱い、オブジェクトの寿命を区別しない方法です。
世代管理のオーバーヘッドがないため、単純で効率的な場合があります。
世代モード(Generational Mode)
オブジェクトを「若い世代(Young Generation)」と「古い世代(Old Generation)」の2つに分類し、それぞれの世代で異なる頻度でGCを行い、効率を向上させます。
- 若い世代:短命なオブジェクトが多く、GC頻度が高い
- 古い世代:寿命が長いオブジェクトが多く、GC頻度は少なめ
ZGCの特徴
世代モードのZGCは、数ミリ秒以下の低レイテンシを維持しつつ、大規模なヒープ(最大16TB)を管理できるスケーラブルなGCです。
主な特徴と実現している技術は下記の通りです。
ZGCの主な特徴
- 低レイテンシ
- ZGCの最大の特徴は、通常、一時停止時間は1ミリ秒未満に抑えられます。これにより、レイテンシに敏感なアプリケーションでも、安定したパフォーマンスを維持できます
- ガベージコレクションのほとんどの処理をアプリケーションスレッドと並行して実行すること(コンカレントGC)により、STW(Stop-The-World)の発生を抑えます
- 高いスループット
- コンカレントGCにより、アプリケーションのスループットを高く維持できます
- Parallel GCのように、高いスループットを追求したGCに比べると劣ると思いますが、並行処理により安定したスループットを維持できます
- スケーラビリティ
- ヒープサイズが大きくなっても、パフォーマンスがほとんど低下しないため、大規模なアプリケーションにも適しています
- 大規模なヒープサイズをサポートし、ヒープが拡大しても安定した性能を維持します
ZGCを実現する技術
- メモリ管理
- メモリを小さなリージョンに区切って管理し、効率的なメモリ再利用します
- 同時再配置
- メモリ断片化を抑えるため、アプリケーション動作中にオブジェクトの再配置を並行して行います
- 同時マーキング
- 到達可能/不可能なオブジェクトのマーキングをアプリケーション処理と並行して行い、不要なオブジェクトを解放します
- カラー・ポインタ(Colored Pointers)
- オブジェクトの状態を3つのカラー(
marked0
、marked1
、remarked
)としてポインタ自体に埋め込み、オブジェクトの参照状態を効率的に管理します-
marked0
とmarked1
の2つの生存マークがあるのは、スレッドが同じオブジェクトに対して同時にマークを試みる際の競合を回避し、効率的にマーク処理を進めるためです - ヒープ全体をスキャンする必要性が減り、GCの処理時間がヒープサイズに比例して増加することを防ぎます
-
- 64bitのメモリアドレスを使用して管理します
- オブジェクトの状態を3つのカラー(
- ロードバリア(Load Barrier)の採用
- スレッドがヒープからオブジェクト参照をロードするときに実行されます
- JIT(Just-In-Time)コンパイラで処理を挿入します
- ロードバリアは、ロードされたオブジェクト参照に不正なカラーがあるかどうかを確認します
- 必要に応じて参照を新しいオブジェクトのアドレスに修正します
- スレッドがヒープからオブジェクト参照をロードするときに実行されます
まとめ
Javaの歴史を振り返りながら、各Javaバージョンで導入されたGCアルゴリズムについて調べました。
大規模なヒープを使用する場合でも、低レンテンシを実現できるGCがあることに驚きました。
いつか、TB級のメモリを積んだマシンでZGCの実力を見てみたいです。
また、物理メモリで用意するのは困難なので、仮想メモリで実現することになると思いますが、頻繁な仮想メモリへのアクセスはパフォーマンスに影響を与えそうです。ZGC自体のチューニングもですが、OSの仮想メモリのページングなども意識しないといけないですね。
他GCの特性も勉強して、アプリケーションに最適なGCを選択できるようにしていきたいと思います。
なお、本記事の内容は私が調査した情報に基づいており、正確性を保証するものではありません。情報の利用に際しては、自己責任でお願いいたします。
Discussion