はじめに:この記事のきっかけ
この記事は、機能追加に伴う性能試験がきっかけで執筆しました。
CPU負荷が高い原因を探る中で「GCが関係しているのでは?」と仮説を立て、jstatコマンドで取得したログを分析しました。観測対象は以下の3点です。
- Young GC の周期
- Old 領域の使用推移
- GC 累積時間(GCT)
「GCは何となく知っているけれど、ログの見方までは詳しくない」という方も多いと思います。そこで、自分の復習も兼ねて整理しました。
前提は Java 21、対象GCは G1GC と ZGC に絞ります。記事は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を調整する。
コメント