Javaにおける参照型の種類とメモリ管理の仕組み

Javaプログラミングにおいて、メモリ管理はガベージコレクション(GC)によって自動化されていますが、オブジェクトのライフサイクルをより詳細に制御したい場合があります。JDKはこのために、強度の異なる4種類の参照型を提供しています。これらは強参照(Strong Reference)ソフト参照(Soft Reference)弱参照(Weak Reference)ファントム参照(Phantom Reference)であり、GCによる挙動がそれぞれ異なります。

1. 強参照 (Strong Reference)

強参照はJavaにおけるデフォルトの参照形式です。通常、newキーワードを使用してオブジェクトを生成し、変数に代入する際に使用されます。強参照が存在する限り、GCはそのオブジェクトを回収せず、メモリ不足(OutOfMemoryError)が発生するまでは保持され続けます。

public class StrongRefDemo {

    private static final int MB = 1024 * 1024;

    private static void logMemory(String phase) {
        Runtime rt = Runtime.getRuntime();
        System.out.printf("\n[%s] Free: %dMB / Total: %dMB%n",
            phase, rt.freeMemory() / MB, rt.totalMemory() / MB);
    }

    public static void main(String[] args) {
        logMemory("初期状態");

        // 10MBのバイト配列を生成し、強参照を保持する
        byte[] dataBlock = new byte[10 * MB];
        logMemory("10MB配列を確保後");
        System.out.println("参照状態: " + (dataBlock != null));

        // GCを実行しても、強参照があるため回収されない
        System.gc();
        logMemory("GC実行後");
        System.out.println("参照状態: " + (dataBlock != null));

        // 参照を解除する
        dataBlock = null;
        logMemory("参照解除後");

        System.gc();
        logMemory("GC実行後(回収対象)");
    }
}

2. ソフト参照 (Soft Reference)

ソフト参照は、メモリが不足している場合にのみGCによって回収される参照です。メモリ余剰がある場合は回収されず、キャッシュなどの実装に適しています。java.lang.ref.SoftReferenceクラスを使用して定義します。

import java.lang.ref.SoftReference;

public class SoftRefDemo {

    private static final int MB = 1024 * 1024;

    private static void logMemory(String phase) {
        Runtime rt = Runtime.getRuntime();
        System.out.printf("\n[%s] Free: %dMB%n", phase, rt.freeMemory() / MB);
    }

    public static void main(String[] args) {
        logMemory("初期状態");

        // 10MBのオブジェクトに対してソフト参照を作成
        SoftReference<byte[]> ref = new SoftReference<>(new byte[10 * MB]);
        logMemory("ソフト参照で10MB確保");
        System.out.println("オブジェクト生存: " + (ref.get() != null));

        // メモリに余裕がある状態でGCを実行
        System.gc();
        logMemory("GC実行後(メモリ余裕あり)");
        System.out.println("オブジェクト生存: " + (ref.get() != null));

        // 追加でメモリを消費し、ヒープ圧を高める
        // これによりGCがソフト参照のオブジェクトを回収しようとする可能性がある
        SoftReference<byte[]> anotherRef = new SoftReference<>(new byte[8 * MB]);
        logMemory("追加メモリ確保後(圧力テスト)");
        System.out.println("元の10MBオブジェクト生存: " + (ref.get() != null));
        System.out.println("新しい8MBオブジェクト生存: " + (anotherRef.get() != null));
    }
}

3. 弱参照 (Weak Reference)

弱参照は、ソフト参照よりもさらに生存期間が短いです。GCが実行されると、メモリの状況に関わらず、弱参照によってのみ保持されているオブジェクトは回収されます。主に、正規化されたマッピングやWeakHashMapなどの実装に利用されます。

import java.lang.ref.WeakReference;

public class WeakRefDemo {

    private static final int MB = 1024 * 1024;

    private static void logMemory(String phase) {
        Runtime rt = Runtime.getRuntime();
        System.out.printf("\n[%s] Free: %dMB%n", phase, rt.freeMemory() / MB);
    }

    public static void main(String[] args) {
        logMemory("初期状態");

        // 弱参照を使用してオブジェクトを保持
        WeakReference<byte[]> ref = new WeakReference<>(new byte[10 * MB]);
        logMemory("弱参照で10MB確保");
        System.out.println("オブジェクト生存: " + (ref.get() != null));

        // GCが実行されると、弱参照のみのオブジェクトは即座に回収される
        System.gc();
        logMemory("GC実行後");
        System.out.println("オブジェクト生存: " + (ref.get() != null));
    }
}

4. ファントム参照 (Phantom Reference)

ファントム参照は、オブジェクトの実体への直接アクセスを提供しません。get()メソッドを呼び出しても常にnullを返します。そのため、単独で使用する意味はほとんどありません。通常はReferenceQueueと組み合わせて使用し、オブジェクトがGCによって回収されたことを検知し、その後の後処理(リソース解放など)を行うために利用されます。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomRefDemo {

    private static final int MB = 1024 * 1024;

    private static void logMemory(String phase) {
        Runtime rt = Runtime.getRuntime();
        System.out.printf("\n[%s] Free: %dMB%n", phase, rt.freeMemory() / MB);
    }

    public static void main(String[] args) throws InterruptedException {
        logMemory("初期状態");
        
        byte[] target = new byte[10 * MB];
        logMemory("オブジェクト生成後");

        // 参照キューとファントム参照の作成
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        PhantomReference<byte[]> phantomRef = new PhantomReference<>(target, queue);

        logMemory("ファントム参照作成後");
        System.out.println("PhantomReference.get(): " + phantomRef.get());
        System.out.println("Queue Status: " + queue.poll());

        // 強参照を解除
        target = null;
        logMemory("強参照解除後");

        System.gc();
        Thread.sleep(100); // GCの完了を待つ
        logMemory("GC実行後");
        
        System.out.println("PhantomReference.get(): " + phantomRef.get());
        System.out.println("Queue Status (Enqueued): " + queue.poll());
    }
}

参照タイプの挙動比較

各参照タイプがガベージコレクションの対象となる条件の違いを以下の表にまとめます。

参照タイプ GC時(メモリ余裕あり) GC時(メモリ不足)
強参照 (Strong) 回収されない 回収されない(OOMまで)
ソフト参照 (Soft) 回収されない 回収される可能性が高い
弱参照 (Weak) 回収される 回収される

7月10日 01:54 投稿