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()を呼ぶように作ってあります。