@(Java)[Reference]
Java Reference 源碼分析
Reference
對象封裝了其它對象的引用,可以和普通的對象一樣操作,在一定的限制條件下,支援和垃圾收集器的互動。即可以使用Reference對象來引用其它對象,但是最後還是會被垃圾收集器回收。程式有時候也需要在對象回收後被通知,以告知對象的可達性發生變更。
Java提供了四種不同類型的引用,引用級别從高到低分别為
FinalReference
,
SoftReference
WeakReference
PhantomReference
。其中
FinalReference
不對外提供使用。每種類型對應着不同級别的可達性。
簡介
強引用 FinalReference
FinalReference
強引用指的是,程式中有直接可達的引用,而不需要通過任何引用對象,如Object obj = new Object();中,obj為強引用。
軟引用 SoftReference
SoftReference
軟引用,非強引用,但是可以通過軟引用對象來通路。軟引用的對象,隻有在記憶體不足的時候(抛出OOM異常前),垃圾收集器會決定回收該軟引用所指向的對象。軟引用通常用于實作記憶體敏感的緩存。
SoftReference<Object> softRef = new SoftReference<Object>(new Object());
弱引用 WeakReference
WeakReference
弱引用,非強引用和軟引用,但是可以通過弱引用對象來通路。弱引用的對象,不管記憶體是否足夠,隻要被垃圾收集器發現,該引用的對象就會被回收。實際的應用見
WeakHashMap
等。
WeakReference<Object> weakRef = new WeakReference<Object>(new Object());
虛引用 PhantomReference
PhantomReference
虛引用,該引用必須和引用隊列(
ReferenceQueue
)一起使用,一般用于實作追蹤垃圾收集器的回收動作,比如在對象被回收的時候,會調用該對象的finalize方法,在使用虛引用可以實作該動作,也更加安全。
Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<Object>(obj, refQueue);
ReferenceQueue
該隊列作為引用中的一員,可以和上述三種引用類型組合使用,該隊列的作用是:建立Reference時,将Queue注冊到Reference中,當該Reference所引用的對象被垃圾收集器回收時,會将該Reference放到該隊列中,相當于一種通知機制。
示例 Demo1:
ReferenceQueue queue = new ReferenceQueue();
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
System.gc();
Reference reference1 = queue.remove();
System.out.println(reference1);
源碼分析
Reference
和 ReferenceQueue
Reference
ReferenceQueue
Reference
内部有幾個比較重要的屬性
// 用于儲存對象的引用,GC會根據不同Reference來特别對待
private T referent;
// 如果需要通知機制,則儲存的對對應的隊列
ReferenceQueue<? super T> queue;
/* 這個用于實作一個單向循環連結清單,用以将儲存需要由ReferenceHandler處理的引用 */
Reference next;
static private class Lock { };
// 鎖,用于同步pending隊列的進隊和出隊
private static Lock lock = new Lock();
// 此屬性儲存一個PENDING的隊列,配合上述next一起使用
private static Reference pending = null;
狀态圖

内部類 ReferenceHandler
ReferenceHandler
ReferenceHandler
作為Reference的靜态内部類,用于實作将pending隊列裡面的
Reference
執行個體依次添加到不同的
ReferenceQueue
中(取決于Reference裡面的queue)。該pending的元素由GC負責加入。
注:這裡對pending隊列進行加鎖,個人認為是因為GC線程可能和ReferenceHandler所在的線程并發執行,如GC采用CMS并發收集的時候。
如下代碼所示
// 此線程在靜态塊中啟動,即一旦使用了Reference,則會啟動該線程
private static class ReferenceHandler extends Thread {
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
// 從pending中取下一個元素,如果後繼為空,則next指向自身 pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
// 沒有則等待,後續加入元素會調用lock.notify喚醒
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// ...
ReferenceQueue q = r.queue;
// 如果該Reference注冊了對應的Queue,則加入到該Queue中
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
ReferenceQueue屬性
// 用于辨別沒有注冊Queue
static ReferenceQueue NULL = new Null();
// 用于辨別已經處于對應的Queue中
static ReferenceQueue ENQUEUED = new Null();
static private class Lock { };
/* 互斥鎖,用于同步ReferenceHandler的enqueue和使用者線程操作的remove和poll出隊操作 */
private Lock lock = new Lock();
// 隊列
private volatile Reference<? extends T> head = null;
// 隊列中的元素個數
private long queueLength = 0;
ReferenceQueue.enqueue
隻會通過Reference裡要調用該方法,用于将Reference放入到目前隊列中
boolean enqueue(Reference<? extends T> r) {
synchronized (r) {
// 判斷是否已經入隊了
if (r.queue == ENQUEUED) return false;
synchronized (lock) {
r.queue = ENQUEUED;
// 單向循環
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
// 通知目前挂起的線程(調用remove時有可能會挂起)
lock.notifyAll();
return true;
}
}
}
ReferenceQueue.remove
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException
{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
// 從隊列中取出一個元素
Reference<? extends T> r = reallyPoll();
// 如果不為空,則直接傳回
if (r != null) return r;
for (;;) {
// 否則等待,由enqueue時notify喚醒
lock.wait(timeout);
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) return null;
}
}
}
具體執行流程
以上述示例Demo1作為分析
// 建立一個引用隊列
ReferenceQueue queue = new ReferenceQueue();
// 建立虛引用,此時狀态為Active,并且Reference.pending為空,目前Reference.queue = 上面建立的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 當GC執行後,由于是虛引用,是以回收該object對象,并且置于pending上,此時reference的狀态為PENDING
System.gc();
/* ReferenceHandler從pending中取下該元素,并且将該元素放入到queue中,此時Reference狀态為ENQUEUED,Reference.queue = ReferenceENQUEUED */
/* 當從queue裡面取出該元素,則變為INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);
應用 - WeakHashMap
WeakHashMap在使用上和HashMap類型,都是Hash + 連結清單解決沖突,唯一不同點在于前者的Key是使用虛引用來實作的,即當進行垃圾回收的時候,就是被回收掉,此時WeakHashMap會在下次操作的時候,根據被回收掉的Key,從Map裡面移除掉。
Entry
當建立Entry的時候,會注冊進目前Map屬性的queue,當key被回收後,則該Entry會被放入到queue中,每當操作Map的時候,才會将原有的Value清除掉。(由expungeStaleEntries方法來進行,并且沒有啟動一個單獨的線程來處理,沒有必要,這樣子簡化了邏輯以及避免鎖的開銷)
// 外部WeakHashMap屬性
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/* 這裡采用了內建WeakReference而不是直接使用,是因為當被回收的時候,具體的Key是不知道的,這裡需要往WeakReference額外加入一些屬性,以便在被回收後通知時,能夠定位到具體的Key/value */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
// 這裡屬性不能加入key,否則會導緻存在強引用而不能被視為WeakReference回收掉
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);
this.value = value;
this.hash = hash;
this.next = next;
}
// ...
}
參考
官方文檔:
http://docs.oracle.com/javase/7/docs/api/java/lang/ref/package-summary.html#package_description
部落格:
http://www.importnew.com/20468.html
http://hongjiang.info/java-referencequeue/
http://blog.csdn.net/lyfi01/article/details/6415726