S-JIS[2008-07-14] 変更履歴

弱参照リスト

ひしだま作の弱参照リスト(WeakList)です。
詳細はJavadocWeakList)参照。

WeakList.java (11.2KB)

このリストにオブジェクトを追加すると、他にそのオブジェクトを参照している箇所(強参照)が無くなったときに(GCが実行されると)リストからそのオブジェクトを削除します。
原理的にはWeakHashMapと同じです。


使用例

ファイルをオープンして使用し、クローズは適当に自動で行うことを考えます。
(本来は使い終わった時点でその都度(finallyで)明示的にクローズすべきですが(苦笑))

	private static WeakList<FileInputStream> fisList = new WeakList<FileInputStream>();
	public static void main(String[] args) throws IOException {
		try {
			loop();
		} finally {
			// (ファイナライザーが実行されていない)全ファイルをクローズ
			for (FileInputStream fis : fisList) {
				if (fis != null) {
					try { fis.close(); } catch(IOException e){}
				}
			}
		}
	}
	static void loop() throws IOException {
		for (int i = 0; i < 100; i++) {
			FileInputStream fis = new FileInputStream("C:/temp/test.txt") {
				@Override
				public void close() throws IOException {	//デバッグ用に、close()をオーバーライド
					System.out.println("close");
					super.close();
				}
			};

			fisList.add(fis);

			fis.read();
			//今回の例では、fisのクローズはしない

			//実験用に、適当な間隔でGCを実行してみる
			if (i % 30 == 0) System.gc();
		}

		System.out.println("loop end");
	}

FileInputStreamでは、ファイナライザーclose()を呼び出すようになっています。
したがって、メモリーが足りなくなってきてGCが実行されると、ファイナライザーが呼ばれてクローズされてからオブジェクトが破棄されます。
WeakListでは そうして破棄されたオブジェクトはリスト内から消されるので、最後に残っているもの(つまりファイナライザー(クローズ)が実行されていないもの)に対して明示的にクローズしてやればよいということになります。

WeakListを使わずに普通のリストでFileInputStreamオブジェクトを管理し、最後に全てクローズするという仕組みも考えられますが…
本当に一番最後になるまで一切クローズされない(リスト自身がオブジェクトを(強参照で)掴んでいるので、GCの対象にならない=ファイナライザーが呼ばれない)のが難点です。
リストが保持する際に弱参照で保持する、というのが大きなポイントという訳です。


使用(仕様)上の注意点

通常のListはadd()等でnullを入れることが出来ますが、WeakListは“参照を保持する”という目的なので、nullは入れられません。nullを保持してどーする。
一方、get()系では、nullが返ってくる可能性があります。当リストが保持しているのはWeakReferenceですが、get()で返しているのはWeakReference#get()の返り値 (元々add()された値)です。これは、GCが動いて対象オブジェクトが破棄されるとnullにクリアされるので、GCとのタイミングによってはnullになることがある訳です。

また、インデックス番号を指定する系統のメソッド(remove(int)等)はほとんど実装していません。
参照を保持するという目的なので、「そのオブジェクトが何番目か」という情報には意味がない為です。
(実装も、LinkedListを参考に作っています)

WeakListは、LinkedListと同様、MTセーフではありません。


WeakListの仕組みの概要

WeakListは、おおよそ以下のような作りになっています(ソースの抜粋)。

package jp.hishidama.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractList;
public class WeakList<E> extends AbstractSequentialList<E> implements Queue<E> {

	/** 参照キュー */
	private ReferenceQueue<E> queue = new ReferenceQueue<E>();

	/** Entryのリストの先頭と末尾を表すオブジェクト */
	private Entry root = new Entry();

	/** リスト内に保持している要素の個数 */
	private int size = 0;
	/** リスト内に保持する要素(いわばラッパークラス) */
	private class Entry extends WeakReference<E> {	//型引数Eを指定したいので、staticクラスに出来ない

		private Entry prev, next;

		private Entry() {
			super(null, null);
			this.prev = this;
			this.next = this;
		}

		private Entry(E referent, Entry next, Entry prev) {
			super(referent, queue);
			prev.next = this;
			this.prev = prev;
			this.next = next;
			next.prev = this;
		}

		private void remove() {
			prev.next = this.next;
			next.prev = this.prev;
	
			this.prev = null;
			this.next = null;
		}
	}
	private void expungeStaleEntries() {
		if (size == 0) {
			return;
		}
		for (;;) {
			Entry e = (Entry) queue.poll();	//ここはダウンキャスト
			if (e == null) {
				break;
			}
			remove(e);
		}
	}

expungeStaleEntries()は、WeakHashMap内に同名のメソッドがあり、同様の処理を行っています。
すなわち、参照キューをpoll()し、Entryが返ってきたら自分(WeakList)の内部から削除しています

	public int size() {
		expungeStaleEntries();
		return size;
	}

expungeStaleEntries()が呼ばれるのは、非同期ではありません。つまり、GCが動いたときに勝手に呼ばれる訳ではないという事です。
WeakListのsize()やadd()など、中身(要素)の増減に関係しそうなメソッドが呼ばれた際にexpungeStaleEntries()を呼ぶように作ってあります。


自作ソフトへ戻る / 技術メモへ行く
メールの送信先:ひしだま