JVMのGC 実践ガイド 前編(GCの基本と発火タイミング)

パフォーマンスチューニング
スポンサーリンク
google.com, pub-5238665064291805, DIRECT, f08c47fec0942fa0

はじめに:この記事のきっかけ

この記事は、機能追加に伴う性能試験がきっかけで執筆しました。
CPU負荷が高い原因を探る中で「GCが関係しているのでは?」と仮説を立て、jstatコマンドで取得したログを分析しました。観測対象は以下の3点です。

  • Young GC の周期
  • Old 領域の使用推移
  • GC 累積時間(GCT)

「GCは何となく知っているけれど、ログの見方までは詳しくない」という方も多いと思います。そこで、自分の復習も兼ねて整理しました。

前提は Java 21、対象GCは G1GCZGC に絞ります。記事は3部構成です。

  • 前編(本記事):考え方と全体像
  • 中編:jstatの使い方とログの読み方
  • 後編:実践レシピとトラブルシュート

GCの基本的な考え方

GCはなぜ必要か

Javaでは、C/C++のようにメモリを手動で解放する必要はありません。
不要になった(到達不能になった)オブジェクトは、ランタイムが自動的に回収します。

  • 利点: 解放忘れ・二重解放・Use-After-Free など典型的なメモリバグを回避できる
  • トレードオフ: 回収にはコスト(停止時間・CPU・メモリ余裕)がかかる
  • 結論: 改修にはコストがかかるため「観測 → 最適化」が不可欠

ユースケースによって最適なGCは異なります。

  • 低レイテンシ重視(p95/p99 レイテンシ) → ZGC
  • スループット・予測可能な停止時間のバランス → G1GC

GCが起きるタイミング(Java21/G1・ZGC)

GCは「いつ走るのか」を押さえることが重要です。ここでは代表的なイベントを整理します。
 ※G1:ヒープ領域を細かく分割し、アプリケーションの実行と並行してGC処理を行うことで、停止時間を短縮する仕組み
 ※ZGC:アプリケーションスレッドの実行を停止させることなく、すべてのGC作業を並行して実行する仕組み

  • Young/Minor GC(共通): 新規割当先(Eden)が埋まってもう割り当てられないと判断された瞬間に発生。スレッドは TLAB を使い切ると Eden から補充する。
  • 並行マークの開始(G1): Old使用率と割当速度(アロケーションレート)から、このままだと逼迫しそうと判断(IHOP)。開始直後/終了時に短いSTW(Initial Mark/Remark)が発生。
  • Mixed GC(G1): 並行マーク完了後、Young回収にOldの一部を混ぜて回収してフルGCを回避。
  • Full GC: 退避失敗(Evacuation Failure)、断片化、Metaspace 逼迫、System.gc() 呼び出し等で発生(避けたい最終手段)。
  • Metaspace圧迫: クラスロード過多で拡張が難しくなり、Full/OOMに波及し得る。
  • ZGCの補足: Mark/Relocate など多くが並行で、STWは極小。その代わりCPUとメモリの余裕が必要。

JVMヒープとGCの基本

ヒープ構成の簡易図

GCを理解するには、ヒープ構成を押さえることが近道です。

+------------------------------- JVM Heap -------------------------------+
|                             Young 世代                                  |
|   [TLAB] -> Eden  -->  Survivor S0 <-> Survivor S1   (ageを重ねる)     |
+------------------------------------------------------------------------+
|                              Old 世代                                   |
|          (長生きオブジェクト。Mixed GCで段階的に回収)                 |
+------------------------------------------------------------------------+
|                    Metaspace / CCS(オフヒープ)                        |
+------------------------------------------------------------------------+

オブジェクトの一生:
new -> TLAB -> Eden -> Survivor -> Survivor -> ... -> Old
  • Young世代: Eden(新規割当)と Survivor(S0/S1)(退避先)。
  • Old世代: 長期間使われるオブジェクトが移動する場所。
  • Metaspace/CCS: クラスメタ情報(オフヒープ)。
  • Stop-The-World (STW): 一部フェーズでアプリを停止。長さ×頻度がレイテンシに直結。
  • TLAB: スレッド私有の割当バッファ。使い切るとEdenから補充。

Java 21で使うGCと使いどころ(絞り込み)

GC名特徴向き/注意主なオプション
G1GC(既定)リージョン分割・並行。ポーズ目標指定で予測しやすい一般的なWeb/API用途で選択-XX:+UseG1GC, -XX:MaxGCPauseMillis=200
ZGC低ポーズ(数ms〜)。大規模ヒープでも安定p99レイテンシ最重視、余裕あるメモリ環境-XX:+UseZGC(Java 21は世代別ZGC)
Parallelスループット重視(停止は長め)バッチ/ツール、レイテンシ要求が緩い-XX:+UseParallelGC

まとめ(前編)

  • GCはオブジェクトの自動解放の仕組み。ただしコストがかかるので観測→最適化する必要がある。
  • Young/並行マーク/Mixed/Full/Metaspaceなどの発火条件を理解すると、jstatの数字がを解釈しやすくなる。
  • 実務では、SLA(レイテンシ)/スループット/メモリを考慮し、G1/ZGCを調整する。

参考

コメント

タイトルとURLをコピーしました