ガベージコレクション
はじめに
ガベージコレクション(GC)は、不要になったオブジェクトを自動的に回収し、メモリを解放する仕組みです。Javaプログラマーは手動でメモリを解放する必要がありませんが、GCの仕組みを理解することでパフォーマンスを最適化できます。
GCの基本概念
到達可能性(Reachability)
GCは「到達可能性」に基づいてオブジェクトの生死を判断します。
┌─────────────────── ──────────────────────────────────────────────────┐
│ 到達可能性の判定 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ GC Roots(起点) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ・スタック上のローカル変数 │ │
│ │ ・static 変数 │ │
│ │ ・JNI参照 │ │
│ │ ・アクティブなスレッド │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Object A │───→│Object B │───→│Object C │ 到達可能 → 生存 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │Object D │───→│Object E │ 到達不能 → 回収対象 │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
GCアルゴリズムの種類
| アルゴリズム | 動作 | 特徴 |
|---|---|---|
| Mark-Sweep | マーク後にスイープ | シンプルだがフラグメント発生 |
| Mark-Compact | マーク後に圧縮 | フラグメント解消、コスト高 |
| Copying | 生存オブジェクトをコピー | 高速、メモリ効率50% |
| Generational | 世代別に異なる戦略 | 現代のGCの基本 |
世代別GC
なぜ世代別か?
弱い世代仮説(Weak Generational Hypothesis):
- ほとんどのオブジェクトは若くして死ぬ
- 長生きしたオブジェクトは更に長生きする傾向がある
┌─────────────────────────────────────────────────────────────────────┐
│ オブジェクトの生存率 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 生存率 │
│ 100% ┤ │
│ │█ │
│ │█ │
│ 50% │█ │
│ │█ █ │
│ │█ █ █ │
│ 0% │█ █ █ █ █ █ █ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ └──────────────────────────────────────────→ 経過時間 │
│ ↑ ↑ │
│ │ │ │
│ ほとんどが 長生きするものは │
│ すぐ死ぬ さらに長生き │
│ │
└─────────────────────────────────────────────────────────────────────┘
Young Generation のGC(Minor GC)
Minor GC の流れ
═══════════════════════════════════════════════════════════════════════
1. GC前
┌──────────────────────────────────────────────────────────────┐
│ Eden │ Survivor0 │ Survivor1 │
│ ████████████████████ │ ██ │ │
│ (新規オブジェクト) │ (前回生存) │ (空) │
└──────────────────────────────────────────────────────────────┘
2. Mark & Copy
┌──────────────────────────────────────────────────────────────┐
│ Eden → 生存オブジェクトを S1 へコピー │
│ S0 → 生存オブジェクトを S1 へコピー(年齢+1) │
└──────────────────────────────────────────────────────────────┘
3. GC後
┌──────────────────────────────────────────────────────────────┐
│ Eden │ Survivor0 │ Survivor1 │
│ │ │ ███ │
│ (空) │ (空) │ (生存オブジェクト) │
└──────────────────────────────────────────────────────────────┘
4. 次回は S0 と S1 の役割が入れ替わる
Old Generation への昇格(Promotion)
┌─────────────────────────────────────────────────────────────────────┐
│ 昇格(Promotion)の条件 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 年齢(Age)が閾値に達した │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ デフォルト: 15回のMinor GCを生き延びると昇格 │ │
│ │ 設定: -XX:MaxTenuringThreshold=15 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 2. Survivor領域がオーバーフロー │
│ ┌─────── ─────────────────────────────────────────────────────┐ │
│ │ Survivor に入りきらないオブジェクトは直接 Old へ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 大きなオブジェクト │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Eden に入りきらない巨大オブジェクトは直接 Old へ │ │
│ │ 設定: -XX:PretenureSizeThreshold │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Major GC / Full GC
| 種類 | 対象 | トリガー |
|---|---|---|
| Minor GC | Young Generation のみ | Eden が満杯 |
| Major GC | Old Generation のみ | Old が一定割合に達した |
| Full GC | 全世代 + Metaspace | 明示的呼び出し、Metaspace満杯等 |
GCコレクターの種類
Java 21で利用可能 なGC
┌─────────────────────────────────────────────────────────────────────┐
│ GCコレクター比較 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Serial GC │ │
│ │ -XX:+UseSerialGC │ │
│ │ ・シングルスレッド │ │
│ │ ・小規模アプリ、クライアント向け │ │
│ │ ・Stop-The-World 時間: 長い │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Parallel GC │ │
│ │ -XX:+UseParallelGC │ │
│ │ ・マルチスレッド │ │
│ │ ・スループット重視 │ │
│ │ ・Stop-The-World 時間: 中程度 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ G1 GC (Garbage-First) ★ Java 9以降デフォルト │ │
│ │ -XX:+UseG1GC │ │
│ │ ・リージョンベース │ │
│ │ ・停止時間目標を設定可能 │ │
│ │ ・大規模ヒープに対応 │ │
│ └──────────────────────────────── ────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ZGC │ │
│ │ -XX:+UseZGC │ │
│ │ ・超低レイテンシ(1ms未満) │ │
│ │ ・TB級ヒープに対応 │ │
│ │ ・コンカレント(ほぼ全フェーズ) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──── ────────────────────────────────────────────────────────────┐ │
│ │ Shenandoah │ │
│ │ -XX:+UseShenandoahGC │ │
│ │ ・超低レイテンシ │ │
│ │ ・コンカレントコンパクション │ │
│ │ ・Red Hat開発 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
G1 GC(デフォルト)
リージョンベースのアーキテクチャ
┌─────────────────────────────────────────────────────────────────────┐
│ G1 GC のヒープ構造 │
├─────────────── ──────────────────────────────────────────────────────┤
│ │
│ 従来のGC │
│ ┌──────────────────────────┐┌────────────────────────────────────┐ │
│ │ Young Generation ││ Old Generation │ │
│ └──────────────────────────┘└────────────────────────────────────┘ │
│ (連続した領域) │
│ │
│ G1 GC(リージョン分割) │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ E │ S │ O │ O │ E │ H │ H │ O │ E │ S │ O │ O │ │ E │ O │ O │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ │
│ E = Eden S = Survivor O = Old H = Humongous │
│ (空 = Free) │
│ │
│ ・リージョンサイズ: 1MB〜32MB(ヒープサイズにより自動決定) │
│ ・Humongous: リージョンの50%以上を占める大きなオブジェクト │
│ │
└────────────────────────────────────────────────────────────── ───────┘
G1 GC のフェーズ
┌─────────────────────────────────────────────────────────────────────┐
│ G1 GC のサイクル │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Young-only フェーズ │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Young GC │────→│ Young GC │────→ ... │ │
│ │ │ (STW) │ │ (STW) │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ │ │ │ │
│ │ Old の使用率が閾値を超えると │ │
│ │ ↓ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Concurrent Mark (並行マーク) │ │ │
│ │ │ ・Initial Mark (STW) - Young GCと同時 │ │ │
│ │ │ ・Root Region Scan │ │ │
│ │ │ ・Concurrent Mark │ │ │
│ │ │ ・Remark (STW) │ │ │
│ │ │ ・Cleanup (STW) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Mixed Collection フェーズ │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Mixed GC │────→│ Mixed GC │────→ ... │ │
│ │ │ (Young+Old)│ │ (Young+Old)│ │ │
│ │ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ゴミの多いリージョンを優先的に回収(Garbage-First) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
G1 GC の設定
java \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \ # 停止時間目標(ミリ秒)
-XX:G1HeapRegionSize=16m \ # リージョンサイズ
-XX:InitiatingHeapOccupancyPercent=45 \ # マーク開始閾値
-jar app.jar
ZGC(低レイテンシ)
特徴
| 項目 | ZGC |
|---|---|
| 最大停止時間 | 1ms未満(ヒープサイズに関係なく) |
| 対応ヒープサイズ | 8MB〜16TB |
| コンカレント処理 | マーク、リロケート、参照処理 |
| カラーポインタ | 64bitポインタにメタデータ埋め込み |
ZGC のアーキテクチャ
┌─────────────────────────────────────────────────────────────────────┐
│ ZGC の特徴 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ カラーポインタ(Colored Pointers) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 64bit ポインタ │ │
│ │ ┌───────────────┬────────────────────────────────────────────┐│ │
│ │ │ メタデータ(4) │ アドレス (44bit) ││ │
│ │ │ (Mark, Remap) │ ││ │
│ │ └───────────────┴────────────────────────────────────────────┘│ │
│ │ → オブジェクトの状態をポインタ自体に格納 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ロードバリア(Load Barriers) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ・オブジェクト参照読み込み時にバリアを挿入 │ │
│ │ ・移動されたオブジェクトを透過的に追跡 │ │
│ │ ・アプリケーションと並行してGC実行可能 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
ZGC の設定
java \
-XX:+UseZGC \
-XX:+ZGenerational \ # 世代別ZGC(Java 21+推奨)
-Xmx16g \
-jar app.jar
Stop-The-World(STW)
STWとは
GC中にアプリケーションスレッドが一時停止する期間です。
┌─────────────────────────────────────────────────────────────────────┐
│ Stop-The-World │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 時間軸 ────────────────────────────────────────────────────────→ │
│ │
│ アプリ ████████████│ │████████████│ │██████████████ │
│ スレッド │ STW │ │ STW │ │
│ │ │ │ │ │
│ GC │██████│ │██████│ │
│ スレッド │ │ │ │ │
│ │
│ STW中: │
│ ・全アプリケーションスレッドが停止 │
│ ・リクエスト処理が中断 │
│ ・レイテンシスパイクの原因 │
│ │
└─────────────────────────────────────────────────────────────────────┘
Safepoint
STWはSafepointでのみ発生します。
// Safepointの例
while (true) {
// ループのバックエッジにSafepoint あり
doWork();
}
// Safepointに到達しないコード(問題になることがある)
for (int i = 0; i < 1_000_000_000; i++) {
// counted loopにはSafepointがない
}
GCログ
有効化
# Java 9以降の統一ログ
java -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=100m -jar app.jar
# 詳細ログ
java -Xlog:gc*=debug:file=gc-debug.log -jar app.jar
ログの読み方
[2024-01-15T10:30:45.123+0900][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 24M->8M(256M) 5.234ms
│ │ │ │ │ │
│ │ │ │ │ └─ 停止時間
│ │ │ │ └─ 総ヒープ
│ │ │ └─ GC後
│ │ └─ GC前
│ └─ GCの原因
└─ GCの種類
GCログ分析ツール
| ツール | 特徴 |
|---|---|
| GCViewer | GUIベース、グラフ表示 |
| GCEasy | Webベース、自動分析 |
| HPjmeter | HP製、詳細分析 |
GC選択の指針
┌─────────────────────────────────────────────────────────────────────┐
│ GC選択フローチャート │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ヒープサイズは? │
│ │ │
│ ├─ < 100MB → Serial GC │
│ │ │
│ ├─ 100MB〜4GB │
│ │ │ │
│ │ ├─ スループット重視 → Parallel GC │
│ │ └─ レイテンシ重視 → G1 GC │
│ │ │
│ └─ > 4GB │
│ │ │
│ ├─ 超低レイテンシ必須 → ZGC or Shenandoah │
│ │ (< 10ms の停止が必要) │
│ │ │
│ └─ 通常 → G1 GC │
│ │
│ ※ 迷ったら G1 GC(デフォルト) │
│ │
└─────────────────────────────────────────────────────────────────────┘
まとめ
| 項目 | ポイント |
|---|---|
| 到達可能性 | GC Roots から辿れるオブジェクトは生存 |
| 世代別GC | Young(頻繁・高速)/ Old(低頻度・低速) |
| G1 GC | リージョンベース、停止時間目標設定可能(デフォルト) |
| ZGC | 超低レイテンシ(1ms未満)、大規模ヒープ対応 |
| STW | GC中のアプリ停止、レイテンシに影響 |
次のステップ
- GCチューニング - 本番環境での最適化方法
- トラブルシューティング - GC関連の問題解決