ガベージコレクションの全体像
ガベージコレクション(GC)は「もう使われないオブジェクトを自動で見つけてメモリを回収する仕組み」です。Javaではメモリ解放を手動で書く必要がなく、プログラムは「必要なときに new で作る」に集中できます。GCの核心は「到達可能性(reachable)」という考え方で、到達不能になったオブジェクトが回収対象になります。
到達可能性と GC ルート
GC ルートとは何か
GC は「どこから辿れるか」を基準に生存を判断します。辿り始める起点が GC ルートです。代表的には、実行中スレッドのスタック上のローカル変数、static フィールド、JNI参照などが GC ルートになります。ルートから参照の鎖を辿っていけるオブジェクトは生存と見なされ、辿れないものが「ゴミ」扱いになります。
具体例(到達可能・到達不能)
static Object keep; // static は GC ルートから辿れる
void demo() {
Object a = new Object(); // スタックから辿れる → 生存
keep = new Object(); // static に保持 → 生存
Object b = new Object(); // 今は生存
b = null; // 参照を失う → 到達不能になれば回収対象
}
Javab = null の瞬間に必ず回収されるわけではありませんが、以降のどこからも辿れないなら、GC タイミングで回収されます。重要なのは「参照を残さないこと」が回収の条件だという点です。
GC のタイミングと「止まる瞬間」(重要ポイントの深掘り)
いつ走るのか
GC はヒープの空きが足りなくなってきたときなどに動きます。用途や選択した GC によって頻度や戦略は異なりますが、プログラマが明示的に「解放」を呼ぶ必要はありません(System.gc() は「お願い」程度で、必ず走る保証はありません)。
Stop-The-World(世界が一瞬止まる)
GC が安全に到達可能性を判定するため、短時間アプリのスレッドが止まる瞬間が発生します。最新の GC ではこの停止時間を短くする工夫が多数ありますが、「停止はゼロではない」前提で設計します。リアルタイム性が厳しい処理では、バッチ化やデータサイズの調整などで影響をならします。
世代別 GC の考え方と主要 GC
若い世代と古い世代
多くの GC は「新しいオブジェクトはすぐ死ぬ、長生きするものは長く生きる」という仮説で最適化します。新規オブジェクトを置く若い領域(Young)と、長生きするものが移る古い領域(Old)に分け、軽い回収を頻繁に、重い回収をたまに、という方針で停止時間を抑えます。
代表的な GC のイメージ
G1 はヒープを小さな領域に分けて回収対象を分散し、停止時間を管理しやすくします。ZGC などの低停止 GC はポインタ修正を工夫して「止まる時間」をさらに小さくします。初心者がまず意識すべき点は、「選ぶ GC によって停止時間の特性が変わるが、基本の到達可能性の考えは同じ」ということです。
ファイナライザとリソース解放の正しい方法(重要ポイントの深掘り)
finalize は使わない
finalize() は不確実で、実行順序やタイミングを保証できず、性能と安全性の問題が大きいため非推奨です。外部リソース(ファイル、ソケット、DB接続)は GC に任せず、明示的に閉じます。
正しいリソース管理
try (var out = new java.io.FileOutputStream("data.bin")) {
out.write(123);
} // ここで自動的に close(try-with-resources)
Javaメモリは GC が回収しますが、OS資源は close() が必要です。try-with-resources を標準にすると、例外時でも確実に解放でき、GC のタイミングに依存しません。
メモリリークの実態と防ぎ方(重要ポイントの深掘り)
Javaでもリークは起こる
「参照を持ち続ける限り GC は回収しない」ため、不要になったオブジェクトをコレクションに入れっぱなし、静的フィールドに保持しっぱなし、といった設計ミスでリークが起きます。GC があるから安全、ではなく「参照の寿命を短く保つ」設計が必要です。
典型例と対策
class Cache {
private static final java.util.Map<String, byte[]> map = new java.util.HashMap<>();
static void put(String k, byte[] v) { map.put(k, v); } // 消し忘れると増え続ける
static void clear() { map.clear(); }
}
Java長寿命のコレクションや static フィールドに巨大データを入れる前に「消す契約」を設計します。必要なら弱参照(WeakReference)や WeakHashMap を使って、参照が失われれば自動で消える構造を選びます。キャッシュの「消滅条件」を明確化するのがリーク防止の第一歩です。
参照の強さ:強・弱・ソフト・ファントム
どの参照がいつ回収されるか
- 強参照は通常の参照。到達可能なら絶対に回収されません。
- 弱参照(
WeakReference)は、到達可能性が弱く、他から参照がなければ次の GC で回収されます。WeakHashMapはキーが弱参照です。 - ソフト参照(
SoftReference)はメモリが逼迫したときに回収されやすく、メモリに余裕があれば残ります(簡易キャッシュ向け)。 - ファントム参照(
PhantomReference)は「ほぼ回収される段階」で通知を得るための特殊用途です。初心者はまず弱参照とWeakHashMapから理解するとよいです。
実例で身につける
例 1: 参照を外すと回収対象になる
public class LoseRef {
public static void main(String[] args) {
byte[] big = new byte[10_000_000]; // 10MB
System.out.println(big.length); // 使う
big = null; // 参照を捨てる → 回収対象
// 以降、必要なら別の配列を割り当ててもメモリ不足になりにくい
}
}
Java例 2: WeakHashMap で「自動消滅」するエントリ
import java.util.*;
public class WeakDemo {
public static void main(String[] args) {
Map<Object, String> m = new WeakHashMap<>();
Object key = new Object();
m.put(key, "value");
key = null; // キー参照を失う
System.gc(); // 実行保証はないが、次のGCでキーが消え得る
System.out.println(m.size()); // 0 になる可能性がある(タイミング依存)
}
}
Java例 3: try-with-resources でメモリ以外のリソースを確実に解放
import java.io.*;
public class SafeIO {
public static void main(String[] args) throws Exception {
try (var br = new BufferedReader(new FileReader("input.txt"))) {
System.out.println(br.readLine());
} // ここで確実に close。GC に任せない。
}
}
Java仕上げのアドバイス(重要部分のまとめ)
GC は「到達不能なら回収される」という単純な原理で動きます。止まる瞬間はあるため、巨大オブジェクトの寿命を短く保ち、不要になった参照は早く捨てる。外部リソースは GC に任せず、try-with-resources で確実に閉じる。リークは「参照を持ち続ける設計」で起きるため、長寿命コレクションや static の扱いを慎重にし、必要なら弱参照やキャッシュ戦略を導入する。GC の種類は違っても「到達可能性」の軸は同じ。まずは参照の寿命管理を癖にして、メモリと停止時間の健全性を保ちましょう。
