Java1.2引入了新的概念——Reference,在這之前都是預設的強引用,即Strong Reference。在GC過程中,只要從GC Roots通過強引用有路徑可達則說明接下來的程式還可能用到,就不能回收,反之則回收。
但是這種方式比較死板,為了增加記憶體回收的靈活性便有了java.lang.ref類庫,裡頭包含最重要的抽象類別Reference,及其三個繼承類:SoftReference(軟引用)、WeakReference(弱引用)和PhantomReference(幻影引用)。當記憶體回收行程正在考察的對象只能通過上述三個中某個Reference對象才可獲得時,這三個Reference衍生類別會為GC提供不同的指示: 當JVM報告記憶體不足的時候,所有只被SoftReference所指向的對象會被GC回收,否則不會回收; 當GC發現一個對象只能通過弱引用對象可達,將會釋放WeakReference所引用的對象。 當GC發現一個對象只能通過幻影引用對象可達,將會將PhantomReference對象插入與其關聯的ReferenceQueue隊列中(PhantomReference對象的初始化必須有一個ReferenceQueue隊列)。而此時PhantomReference所指向的對象只是執行了finalize方法,但是對象本身並沒有被GC回收,要等到ReferenceQueue被真正的處理(如調用remove方法)後才會被回收。
那麼上述功能是如何完成的呢。下面藉助JDK源碼簡單分析下:
在Reference類中有個域referent用來儲存所指對象的引用:
private T referent;/* -- Constructors -- */Reference(T referent) { this(referent, null);}Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}
在初始化Reference對象時,referent會指向傳遞進來的對象。在GC時如果發現目標對象只有Reference對象中的referent指向時,就會根據上述三條規則執行回收策略。我們看到第二個建構函式另外包括一個ReferenceQueue對象,我們很多後續操作都需要依靠這個對象完成,那這個對象是幹嘛用的呢。
GC回收Reference所指對象後(這裡實際指referent所指向的對象,另外PhantomReference是在回收前,finalize後),將Reference添加到與其綁定的ReferenceQueue中,程式通過調用ReferenceQueue的remove方法或poll方法來感知reference被GC回收的事件。下面是一段Reference的源碼:
static private class Lock { };private static Lock lock = new Lock();/* List of References waiting to be enqueued. The collector adds* References to this list, while the Reference-handler thread removes* them. This list is protected by the above lock object.*/ private static Reference pending = null; /* High-priority thread to enqueue pending References */ private static class ReferenceHandler extends Thread { ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } } } static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); }
ReferenceHandler這個線程在Reference類初始化的時候就作為守護進程啟動。注意這裡有一個Reference類型的成員變數pending,ReferenceHandler會迴圈檢測pending是否為空白,如果為空白就掛起等待。當Reference所指向的對象在GC階段被回收,就會被添加到pending上,然後喚醒ReferenceHandler,將Reference加入ReferenceQueue中。現在就可以利用ReferenceQueue做一些後續操作了,就拿經典的WeakHashMap來說一說,先看看它的Entry:
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();/** * The entries in this hash table extend WeakReference, using its main ref * field as the key. */private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; int hash; Entry<K,V> next; Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { super(key, queue); //用key來初始化referent this.value = value; this.hash = hash; this.next = next; } //省略}
Entry繼承了WeakReference,所以它有了弱引用的性質。這裡讓弱引用指向key值所指的對象,所以當沒有其他更強的引用指向key所指對象時,目標對象會被GC掉,並將持有這個key的Entry加入queue所指的ReferenceQueue中去。既然key值都冇得了,自然這個Entry就沒有存在的意義了,所以得找個機會把它哢嚓掉。WeakHashMap把這個時間點設定到了每一次調用getTable或size方法時,首先進行一次清掃工作,而這個清掃工作正是依賴於引用隊列queue中記錄的引用對象。