ThreadLocalと弱参照の関係を深く理解する

弱参照とは

弱参照の特徴を一言で述べると、次のガベージコレクションが発生した際に、メモリの空き容量に関係なく回収対象となるというものです。以下のコード例を見ると、その挙動がより明確になります。

WeakReference<byte[]> weakRef = new WeakReference<>(new byte[1024 * 1024 * 10]);
System.out.println(weakRef.get() != null); // true を出力
weakRef = null;
System.gc();
System.out.println(weakRef.get() != null); // false を出力(GC後はnullになる)

ThreadLocalの仕組み

ThreadLocalは、マルチスレッド環境における変数の競合を回避するための仕組みです。各スレッドが独自の変数コピーを持つことで、共有リソースへの同時アクセスによる問題を防ぎます。ただし、誤解しやすい点がいくつかあります。

ThreadLocalとスレッドの関係

一見すると、データはThreadLocalオブジェクト自体に保存されているように思えますが、実際は異なります。ThreadLocalは単なるキーとして機能し、実際のデータは各スレッド内部のThreadLocalMapに保持されます。

ThreadLocal<Object> localData = new ThreadLocal<>();
localData.set(new Object());
// ビジネスロジック
Object data = localData.get();

各スレッドは内部にThreadLocalMapというマップを持ち、そのキーがThreadLocalインスタンス、値が設定したオブジェクトです。さらに、マップ内のEntryは弱参照を継承したクラスとして実装されています。

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

つまり、スレッド内のThreadLocalMapから、特定のThreadLocalオブジェクトをキーとして値を取得する構造になっています。

ThreadLocalMapのEntryにおける弱参照の役割

なぜEntryは弱参照を使うのでしょうか?一般的な答えは「メモリリークを防ぐため」ですが、その仕組みと限界を詳しく見ていきましょう。

次のようなコードを考えます。

while (true) {
    ThreadLocal<byte[]> local = new ThreadLocal<>();
    local.set(new byte[1024 * 1024 * 10]);
}

このループは、弱参照のおかげで長時間実行してもメモリ不足になりません。ループごとに生成されるThreadLocalオブジェクトは、そのスコープを抜けると到達可能性が失われ、GCの対象となります。ThreadLocalMap内にも参照が残っていますが、それは弱参照なのでGCの妨げにはなりません。このとき、Entryのキーはnullになりますが、Entry自体はまだ存在しています。ただし、Entry#get()を呼び出すとnullが返ります。また、setgetの呼び出し時に、キーがnullEntryは自動的に掃除されるため、完全にメモリが解放される仕組みです。

しかし、次の例はどうでしょう。

ThreadLocal<byte[]>[] refs = new ThreadLocal[10000];
int index = 0;
while (true) {
    ThreadLocal<byte[]> local = new ThreadLocal<>();
    local.set(new byte[1024 * 1024 * 10]);
    refs[index] = local;
    index++;
}

配列のインデックスが上限に達する前に、OutOfMemoryErrorが発生します。なぜなら、ThreadLocalオブジェクトへの強参照がrefs配列に残っているため、GCで回収されないからです。このように、弱参照は万能ではなく、不要になったThreadLocalremove()メソッドを使って明示的にクリーンアップすることが推奨されます。

タグ: ThreadLocal 弱参照 Java メモリリーク ガベージコレクション

7月14日 00:13 投稿