概要
ガベージコレクション(GC)は Java が自動で行ってくれるメモリ管理ですが、その動作原理を把握していないと、本番環境での停止時間増大やメモリ不足に対処できません。とくに業務バッチや API サーバーでは「なぜ急に遅くなったのか」「GC ログのどこを見ればいいのか」という問い合わせが現場で繰り返し発生します。この記事では、Young Generation と Old Generation の世代構造、Minor GC と Full GC の違い、Runtime API を使ったメモリ状態の取得方法を扱います。finalize() が非推奨になった経緯と Cleaner への移行についても触れるので、レガシーコードの保守にも役立ちます。
使いどころ
夜間バッチで処理時間が徐々に伸びている原因を GC ログから切り分ける
API サーバーのレスポンスタイムが不安定なとき、Full GC の発生頻度を確認する
レガシーコードの finalize() を Cleaner または try-with-resources に移行する
コード例
import java.lang.ref.Cleaner;
public class GcBasicDemo {
// メモリ状態を record で表現(Java 17+)
record MemoryStatus(String label, long usedMb, long freeMb,
long totalMb, long maxMb) {
void print() {
System.out.println("--- " + label + " ---");
System.out.println(" 使用中: " + usedMb + " MB");
System.out.println(" 空き: " + freeMb + " MB");
System.out.println(" 合計: " + totalMb + " MB");
System.out.println(" 最大: " + maxMb + " MB");
}
}
/** Runtime からメモリ状態を取得 */
static MemoryStatus captureMemory(String label) {
var rt = Runtime.getRuntime();
var total = rt.totalMemory();
var free = rt.freeMemory();
return new MemoryStatus(label,
(total - free) / (1024 * 1024),
free / (1024 * 1024),
total / (1024 * 1024),
rt.maxMemory() / (1024 * 1024));
}
public static void main(String[] args) throws InterruptedException {
captureMemory("初期状態").print();
// 短命オブジェクトを大量生成(Young Generation に配置される)
System.out.println("\n短命オブジェクトを大量生成...");
for (int i = 0; i < 10_000; i++) {
var temp = new String("一時データ " + i);
}
captureMemory("短命オブジェクト生成後").print();
// GC 要求(動作確認用。本番コードでは使わない)
System.gc();
Thread.sleep(100);
captureMemory("GC 後").print();
// GC 世代構造の説明
var explain = """
=== GC 世代構造 ===
Young Generation : 新しいオブジェクト置き場。Minor GC で頻繁に回収。
Old Generation : 長生きしたオブジェクト。Full GC でまとめて回収。
Minor GC : Young のみ対象(短時間・低コスト)
Full GC : Old を含む全 GC(長時間・アプリ一時停止)
""";
System.out.println(explain);
// Cleaner の使用例(finalize() の代替)
var cleaner = Cleaner.create();
var resource = new Object();
cleaner.register(resource, () ->
System.out.println("Cleaner: リソースが GC されました"));
}
}Version Coverage
デフォルト GC は G1GC(Java 9 から)。GC ログは -Xlog:gc* に統一。record でメモリ状態を不変オブジェクトとして扱え、Cleaner が finalize() の代替として定着。
// Java 17: record でメモリ状態を不変オブジェクトとして管理
record MemoryStatus(String label, long usedMb, long freeMb, long totalMb, long maxMb) {
void print() {
System.out.println("--- " + label + " ---");
System.out.println(" 使用中: " + usedMb + " MB");
}
}
var runtime = Runtime.getRuntime();
var status = new MemoryStatus("現在",
(runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024),
runtime.freeMemory() / (1024 * 1024),
runtime.totalMemory() / (1024 * 1024),
runtime.maxMemory() / (1024 * 1024));Library Comparison
注意点
System.gc() は GC の実行を保証しない。あくまで JVM への要求であり、本番コードに入れるべきではない
finalize() は Java 9 で非推奨、Java 18 で削除対象になった。リソース解放には try-with-resources か Cleaner を使う
Young Generation のサイズが小さすぎると Minor GC が頻発し、オブジェクトが早期に Old Generation へ昇格して Full GC を招く
GC ログの出力形式は Java 8 以前(-XX:+PrintGCDetails)と Java 9 以降(-Xlog:gc*)で異なる。バージョンに応じたオプションを確認すること
FAQ
いいえ。System.gc() は JVM に GC を要求するだけで、実行は保証されません。タイミングも JVM 実装に依存するため、本番コードに入れると逆に性能を落とすことがあります。
GC ログで確認します。Minor GC は Young Generation のみ対象で数ミリ秒、Full GC は Old Generation を含む全領域対象で数百ミリ秒以上かかることが多いです。-Xlog:gc* で出力を有効にしてください。
ファイルやソケットなど外部リソースには try-with-resources(AutoCloseable)を使います。ネイティブメモリの解放など特殊な用途では java.lang.ref.Cleaner が代替になります。