GCとは何か
Java で開発をしていると、よく耳にする「GC(Garbage Collection)」。
これは 不要になったオブジェクトを自動で回収してメモリを解放する仕組み のことです。C言語のように手動で free()
を呼ぶ必要はなく、Java VM が裏側でメモリ管理を行います。
ざっくり構造・最近のGC
-
世代別回収:Eden/Survivor(若世代)→ Old(老世代)
-
Minor GC:Edenが埋まったら短命オブジェクト中心に回収
-
Major/Full GC:Oldが逼迫、断片化、クラス/メタ領域逼迫などで広域回収
-
既定GC:G1GC(Java 9+)。低停止要求は ZGC / Shenandoah も選択肢
主なトリガ
-
Eden満杯(Minor) / Old高水準(Major)
-
巨大配列(Humongous)割当て(G1)
-
System.gc()
明示呼び出し -
メタスペース/オフヒープ圧迫(DirectByteBuffer/JNI など)
“悪い例 → 良い例”で学ぶメモリ/GC対策
1) 無制限キャッシュ(静的Map地獄)
悪い例
ポイント:上限なしは必ずOldを膨らませる。キャッシュは 容量・期限・エビクションを設計。
2) リスナ/コールバック未解除
悪い例
3) ThreadLocal
の放置(プールスレッドに張り付く)
悪い例
良い例(finallyで確実に除去)
4) System.gc()
乱用
悪い例
5) ラムダ/内部クラスが外側(巨大オブジェクト)をキャプチャ
悪い例
良い例(必要最小限のデータだけ渡す・static化)
ポイント:キャプチャ=保持。意図せず大物を延命していないか疑う。
6) ループ内の大量一時オブジェクト
悪い例
良い例(StringBuilder
再利用・ボクシング回避)
7) finalize/Cleaner頼み(遅延・不確実)
悪い例
8) クラスローダ・アプリ再デプロイ時のリーク
悪い例
良い例(クラスローダ境界を越える参照を断つ)
9) 巨大配列・Humongous割当ての長期保持(G1)
悪い例
良い例(分割・ストリーミング・寿命短縮)
ポイント:巨大ブロックは断片化と回収コスト増の温床。
10) 無制限のキュー/バッファ
悪い例
良い例(有界+バックプレッシャ)
GCログ・計測の始め方(JDK 9+)
-
まずはコードの割当て削減 → その後にヒープ/GC調整
-
監視:
jcmd <pid> GC.heap_info
/jstat -gc <pid> 1000
-
ボトルネック特定:JFR(Java Flight Recorder) で割当てホットスポットを把握
-
必要なら ZGC/Shenandoah も評価(レイテンシ目標に応じて)
実務チェックリスト(配布推奨)
-
System.gc()
を禁止/抑制 -
キャッシュ・キューは有界+期限
-
ThreadLocal は finally で remove
-
リスナ/コールバックは確実に解除(AutoCloseable化が効く)
-
ループ内の一時オブジェクトを減らす(Builder再利用/ボクシング回避)
-
巨大配列は分割・短命化
-
クラスローダ境界を跨ぐ静的参照禁止、Executor停止・ドライバ解除
-
try-with-resourcesでオフヒープ即時解放
-
GCログ/JFRで事実ベースに調整
-
目標停止時間(例:
MaxGCPauseMillis
)を定めて検証
まとめ
GCは“自動”でも“万能”ではありません。
「GCが働きやすいコード」(不要参照を残さない・波及して大物を掴ませない・ピークメモリを避ける)を心がけ、ログ/計測で改善ループを回すのが最短距離です。