CodeGym /コース /JAVA 25 SELF /ガベージコレクタ: G1、ZGC、Shenandoah、比較

ガベージコレクタ: G1、ZGC、Shenandoah、比較

JAVA 25 SELF
レベル 64 , レッスン 1
使用可能

1. GC 入門

C や C++ を書いたことがあるなら、free()delete で手動でメモリ解放を行う必要があるのをご存じでしょう。Java はもっと簡単です。new でオブジェクトを作るだけで、削除は不要です—専用の「掃除屋」であるガベージコレクタ(Garbage CollectorGC)が担当します。

GCJVM の一部で、参照がなくなったオブジェクトの占有メモリを自動で解放します。これにより、Java 開発者はメモリ解放忘れ(リーク)や、逆にまだ必要なオブジェクトを誤って削除してクラッシュ、という心配をしなくて済みます。

しかしどんな掃除屋も完璧ではありません。GC が最悪のタイミングで「大掃除」(Stop-the-World)を始めてしまったり、期待ほど速く動かないこともあります。そこで JVM には複数の実装が用意されており、適切なものを選ぶことでアプリの性能に大きな差が生まれます。

主なガベージコレクタの種類

Serial GC

  • Serial GC — 最もシンプルで古いガベージコレクタ。
  • 単一スレッドで動作。
  • 回収中は他のスレッドをすべて停止(Stop-the-World)。
  • マルチスレッドを多用しない小規模アプリに適する。
  • 有効化フラグ: -XX:+UseSerialGC

Parallel GC

  • Parallel GC(別名「Throughput Collector」)。
  • 複数スレッドで回収を実行。
  • 最大スループットを重視。
  • Stop-the-World の停止はあるが、Serial より高速。
  • 短い停止が許容されるサーバー向けアプリに適する。
  • 有効化フラグ: -XX:+UseParallelGC

CMS (Concurrent Mark Sweep)

  • CMS — 既に古いが長く使われた、停止時間を抑える GC。
  • アプリと部分的に並行して動き、停止時間を短縮。
  • 設定がやや複雑でオーバーヘッドもある。
  • Java 9 で廃止予定(deprecated)とマーク。
  • 有効化フラグ: -XX:+UseConcMarkSweepGC

G1 (Garbage First)

  • G1 GC — 最新のデフォルトコレクタ(Java 9 以降)。
  • 停止時間の最小化と性能のバランスを取る。
  • ヒープを多数の小さなリージョンに分割(リージョン型モデル)。
  • ヒープ全体ではなく、リージョンを選択的に回収できる。
  • 目標とする最大停止時間を指定可能(例: -XX:MaxGCPauseMillis=200)。
  • 有効化フラグ: -XX:+UseG1GC(通常は不要。G1 がデフォルト)。

ZGC と Shenandoah

  • ZGCShenandoah — 近代的な低遅延 GC。
  • 目的は最小の停止(ミリ秒単位)、巨大なヒープ(テラバイト級)でも。
  • アプリとほぼ完全に並行して動作。
  • 要件: Java 11+(ZGC)または Java 12+(Shenandoah)。
  • レイテンシに厳しいシステム(取引所、フィンテック、リアルタイム分析)に適する。
  • 有効化フラグ: -XX:+UseZGC または -XX:+UseShenandoahGC

3. 現代的 GC の動作原理

Young/Old 世代(Young/Old Generation)

JVM はヒープを大きく二つに分けます:

Young 世代(Young Generation): 新しく生成されたオブジェクトが入ります。ここは回収頻度が高く、処理も速い(Minor GC)。

Old 世代(Old Generation, Tenured): Young で複数回の回収を生き延びたオブジェクトが昇格してきます。回収頻度は低いが、時間は長い(Major/Full GC)。

なぜこうするのか。Java の多くのオブジェクトは非常に短命(例: 一時的な文字列やメソッド内のコレクション)だからです。Young の回収を頻繁かつ高速に行い、Old を触らずに済ませられます。

Minor GC

  • Young 世代のみを回収。
  • 高速で停止は短い。
  • Old のオブジェクトは対象外。

Major (Full) GC

  • ヒープ全体(Young と Old)を回収。
  • 時間がかかることがある(巨大ヒープでは秒以上)。
  • 通常は長いアプリ停止を伴う。

GC はどのオブジェクトを削除すべきかをどう判断するか?

GC はコアとなる参照集合(root set。スレッドのスタック上のローカル変数、static フィールド、メソッド引数など)から「生きている」オブジェクトを探索します。到達可能なものは生存、そうでないものはゴミです。

4. 現代的 GC の比較: G1、ZGC、Shenandoah

最新で広く使われる GC の違いを整理します。以下の表をご覧ください。

コレクタ 主目的 メモリモデル 最小停止時間 スケーラビリティ サポート 適用シナリオ
G1 停止時間と速度のバランス リージョン 約10〜200ミリ秒 数百GBまで Java 9+(デフォルト) 大半のサーバーアプリケーション
ZGC 最小停止 リージョン、カラーリング <10ミリ秒 テラバイト級まで Java 11+ リアルタイム、レイテンシクリティカル
Shenandoah 最小停止 リージョン、カラーリング <10ミリ秒 テラバイト級まで Java 12+(RedHat) リアルタイム、レイテンシクリティカル

G1 GC: Garbage First

  • ヒープを多数のリージョン(通常 1〜32MB)に分割。
  • 回収時にゴミの多いリージョンを優先して選択(garbage first)。
  • ヒープ全体ではなく一部のみを回収可能。
  • 目標停止時間を指定可能: -XX:MaxGCPauseMillis=200
  • 速度と停止のバランスに優れ、Java 9 以降デフォルト。

有効化の例(無効化されている場合):

java -XX:+UseG1GC -jar myapp.jar

ZGC: Z Garbage Collector

  • Java 11 では実験的、Java 15 で安定化。
  • アプリをほとんど停止させない: ヒープが 1〜2TB でも停止は通常 <10ミリ秒。
  • カラーリング(coloring)と特殊ポインタを使用。
  • 64bit の JVM が必要。32bit システムでは動作しない。
  • Linux、macOS、Windows をサポート。

有効化の例:

java -XX:+UseZGC -jar myapp.jar

Shenandoah

  • RedHat によって開発。目標は ZGC と近い。
  • 最小停止で、アプリと積極的に並行動作。
  • Linux と Windows をサポート。OpenJDK の一部ビルドに含まれる。
  • 類似の技術を用いるが、内部アルゴリズムは異なる。

有効化の例:

java -XX:+UseShenandoahGC -jar myapp.jar

ビジュアル比較

graph TD
    A[Young世代] -->|Minor GC| B[Old世代]
    B -->|Major GC| C[GC Pause]
    D[G1: リージョン] --> E[選択的リージョン]
    F[ZGC/Shenandoah: リージョン] --> G[並行回収]

5. 実践: 使用中の GC を確認・変更する方法

どの GC が使われているかを確認する方法

  1. JVM ログ: アプリを -Xlog:gc*(Java 9+)または -verbose:gc(Java 8 以前)で起動します。 ログに、使用中の GC や停止の頻度が表示されます。
  2. jcmd: 次を実行:
    jcmd <pid> VM.flags
    
    ここで <pid> は Java プロセスの識別子(PID)です。
  3. jvisualvm: 「Monitoring」セクションで GC の種類を確認できます。

アプリで GC を変更するには?

Java プログラムの起動時に必要なフラグを付けます:

G1 GC(デフォルト。明示も可):

java -XX:+UseG1GC -jar myapp.jar

ZGC:

java -XX:+UseZGC -jar myapp.jar

Shenandoah:

java -XX:+UseShenandoahGC -jar myapp.jar

ヒープサイズと停止時間の設定

  • 最大ヒープサイズ: -Xmx2G
  • 最小ヒープサイズ: -Xms512M
  • G1 の目標停止時間: -XX:MaxGCPauseMillis=200

起動例(フル):

java -Xms512M -Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar myapp.jar

6. 用途別 GC 選定の要点

G1 を選ぶべきとき

  • 多くのサーバー/デスクトップアプリにおけるデフォルト選択として優秀。
  • ヒープが数百MB〜数百GBの範囲で良好に動作。
  • 速度と停止のバランスが良い。

ZGC または Shenandoah を選ぶべきとき

  • アプリが遅延に敏感(レイテンシクリティカル: 取引所、オンラインゲーム、リアルタイム分析)。
  • ヒープが非常に大きい(数百GB以上)。
  • 許容される停止がミリ秒レベルのみ。
  • 要件: Java 11+(ZGC)または Java 12+(Shenandoah)。

Parallel GC で十分なとき

  • スループット重視で、停止が致命的でない小規模アプリ。
  • 停止(Full GC)を「やり過ごせる」バッチ処理。

7. 例: シンプルなアプリでの GC 挙動比較

大量の一時オブジェクトを生成する小さなアプリ(注文処理のシミュレーション):

public class GCSimulator {
    public static void main(String[] args) {
        while (true) {
            // 毎ループで 100,000 個のオブジェクトを作成
            for (int i = 0; i < 100_000; i++) {
                String s = new String("Order-" + i);
            }
            // 少し休む
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

異なる GC で起動してログを確認する:

java -Xmx256M -XX:+UseG1GC -Xlog:gc* GCSimulator
java -Xmx256M -XX:+UseZGC -Xlog:gc* GCSimulator

何が起きるか?
G1 は短い停止を高頻度で行います。ZGC/Shenandoah は停止がさらに短い一方で、発生頻度は高くなる場合があります。Parallel GC は停止が長いものの頻度は低めです。

8. GC 利用時の典型的な落とし穴と注意点

誤り1: GC がメモリ問題をすべて解決してくれると思い込む。 GC は魔法の杖ではありません。不要なオブジェクトへの参照を保持し続ければ、どんな GC でもメモリリークは避けられません。

誤り2: System.gc() を強制呼び出しする。 JVM はいつ回収すべきかを自動的に最適化しています。強制 GC は長い停止を招き、性能を落とすことがあります。

誤り3: GC ログを無視する。 GC ログを見ないと、アプリが定期的に Full GC で「固まっている」事実に気付けないことがあります。

誤り4: 古い GC を使い続ける。 たとえば CMS は既に開発されていません。G1 や最新の低遅延コレクタへ移行しましょう。

誤り5: 用途に合わない GC を選ぶ。 レイテンシクリティカルなアプリで Parallel GC を選べば長い停止を招きます。バッチ処理で ZGC を有効にすれば余計なオーバーヘッドになります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION