S-JIS[2008-07-14] 変更履歴
ひしだま作の弱参照リスト(WeakList)です。
詳細はJavadoc(WeakList)参照。
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は、おおよそ以下のような作りになっています(ソースの抜粋)。
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()を呼ぶように作ってあります。