WeakHashMapの神話


広大なJava界では、WeakHashMapについてずっとこのような伝説が存在しています.
 
 WeakHashMap  ,           ,        

 しかし、WeakHashMapは本当に自動的にエントリを削除しますか?
 
今日は暇なので、WeakHashMapが内部の不要なエントリを自動的に削除してメモリを自動的に解放する目的をどのように実現しているかを見てみたいと思います.JVMが持つソースコードの実装をよく見ると、WeakHashMapでは主にexpungeStaleEntriesという関数によって実現されています.基本的にはWeakHashMapのコンテンツにアクセスするだけでこの関数が呼び出され、内部に外部参照されていないエントリが消去されます.しかし、あらかじめWeakHashMapが生成されていて、GC以前にこのWeakHashMapにアクセスしていなければ、メモリを解放できないのではないでしょうか.
 
コードを書いてテストします.
	public static void main(String[] args) throws Exception {

		List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

		for (int i = 0; i < 1000; i++) {
			WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
			d.put(new byte[1000][1000], new byte[1000][1000]);
			maps.add(d);
			System.gc();
			System.err.println(i);


		}

	}

 
Javaのデフォルトメモリは64 Mなので、メモリパラメータを変更しない場合、このテストは数歩も走らないうちにメモリがオーバーフローします.案の定、WeakHashMapはこの時、不要なメモリを自動的に解放してくれなかった.
 
mapにアクセスするテストを追加してみます.
public static void main(String[] args) throws Exception {

		List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

		for (int i = 0; i < 1000; i++) {
			WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
			d.put(new byte[1000][1000], new byte[1000][1000]);
			maps.add(d);
			System.gc();
			System.err.println(i);

			for (int j = 0; j < i; j++) {
				System.err.println(j+  " size" + maps.get(j).size());
			}
		}
	}

 
このテストは無事に合格した.
 
まとめて言えば、WeakHashMapはあなたが何をしても内部の不要なオブジェクトを自動的に解放するのではなく、あなたがその内容にアクセスしたときに内部の不要なオブジェクトを解放するのです.この二つの言葉はあまり違いがないように見えますが、時には小さな違いが命を落とすことがあります.