Zenn
🧹

JavaのGC:ZGCとは?

2025/03/14に公開

はじめに

はじめまして、三菱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つのカラー(marked0marked1remarked)としてポインタ自体に埋め込み、オブジェクトの参照状態を効率的に管理します
      • marked0marked1 の2つの生存マークがあるのは、スレッドが同じオブジェクトに対して同時にマークを試みる際の競合を回避し、効率的にマーク処理を進めるためです
      • ヒープ全体をスキャンする必要性が減り、GCの処理時間がヒープサイズに比例して増加することを防ぎます
    • 64bitのメモリアドレスを使用して管理します
  • ロードバリア(Load Barrier)の採用
    • スレッドがヒープからオブジェクト参照をロードするときに実行されます
      • JIT(Just-In-Time)コンパイラで処理を挿入します
    • ロードバリアは、ロードされたオブジェクト参照に不正なカラーがあるかどうかを確認します
    • 必要に応じて参照を新しいオブジェクトのアドレスに修正します

まとめ

Javaの歴史を振り返りながら、各Javaバージョンで導入されたGCアルゴリズムについて調べました。
大規模なヒープを使用する場合でも、低レンテンシを実現できるGCがあることに驚きました。
いつか、TB級のメモリを積んだマシンでZGCの実力を見てみたいです。
また、物理メモリで用意するのは困難なので、仮想メモリで実現することになると思いますが、頻繁な仮想メモリへのアクセスはパフォーマンスに影響を与えそうです。ZGC自体のチューニングもですが、OSの仮想メモリのページングなども意識しないといけないですね。

他GCの特性も勉強して、アプリケーションに最適なGCを選択できるようにしていきたいと思います。

なお、本記事の内容は私が調査した情報に基づいており、正確性を保証するものではありません。情報の利用に際しては、自己責任でお願いいたします。

参考文献

Discussion

ログインするとコメントできます