若干年前看了Java的四種引用類型,只是簡單知道了不一樣類型的做用,但對其實現原理一直未能想明白,本文嘗試結合jdk,openjdk6的部分源碼分析弱引用實現的原理,供你們參考,部分技術細節沒有仔細研究,若有疑問歡迎留言討論java
咱們以WeakHashMap的處理過程爲例介紹一個weak reference的生命週期,首先咱們調用WeakHashMap的put方法放入對象到Map中,WeakHashMap的Entry繼承了WeakReferenceweb
- private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
- private V value;
- private final int hash;
- private Entry<K,V> next;
下面是put的部分代碼jvm
- Entry<K,V> e = tab[i];
- tab[i] = new Entry<K,V>(k, value, queue, h, e);
- if (++size >= threshold)
- resize(tab.length * 2);
- return null;
- }
注意new Entry傳遞了一個reference queue到構造函數中,此構造函數最終會調用Reference的構造函數ide
- Reference(T referent, ReferenceQueue<? super T> queue) {
- this.referent = referent;
- this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
- }
referent是咱們以前傳入的hashmap的key對象,queue的做用是用來讀取referent被回收的weak reference,生產者是誰後續介紹,此時WeakHashMap中已經存在了一個對象,先將key對象的strong ref制空並嘗試觸發gc,好比使用System.gc()來顯式的觸發gc,而後調用WeakHashMap的size方法返回集合的個數,絕大多數狀況下會是0,這個過程當中發生了什麼呢?函數
第一步,key沒有可達的strong ref,僅僅存在一個weak reference的referent變量仍然指向了key,觸發GC時,以openjdk6的parNew爲例,jvm在young generation gc時會嘗試獲取Reference對象裏的靜態全局鎖源碼分析
- /* Object used to synchronize with the garbage collector. The collector
- * must acquire this lock at the beginning of each collection cycle. It is
- * therefore critical that any code holding this lock complete as quickly
- * as possible, allocate no new objects, and avoid calling user code.
- */
- static private class Lock { };
- private static Lock lock = new Lock();
在openjdk6裏的部分源代碼,完整代碼請參考instanceRefKlass.cpp文件測試
- void instanceRefKlass::acquire_pending_list_lock(BasicLock *pending_list_basic_lock) {
- // we may enter this with pending exception set
- PRESERVE_EXCEPTION_MARK; // exceptions are never thrown, needed for TRAPS argument
- Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock());
- ObjectSynchronizer::fast_enter(h_lock, pending_list_basic_lock, false, THREAD);
- assert(ObjectSynchronizer::current_thread_holds_lock(
- JavaThread::current(), h_lock),
- "Locking should have succeeded");
- if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION;
- }
此處代碼在parNew gc時執行,目的就是嘗試獲取全局鎖,在gc完成後,jvm會將key被回收的weak reference組成一個queue並賦值到Reference的pending屬性而後釋放鎖,參考方法:ui
- void instanceRefKlass::release_and_notify_pending_list_lock(
- BasicLock *pending_list_basic_lock) {
- // we may enter this with pending exception set
- PRESERVE_EXCEPTION_MARK; // exceptions are never thrown, needed for TRAPS argument
- //
- Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock());
- assert(ObjectSynchronizer::current_thread_holds_lock(
- JavaThread::current(), h_lock),
- "Lock should be held");
- // Notify waiters on pending lists lock if there is any reference.
- if (java_lang_ref_Reference::pending_list() != NULL) {
- ObjectSynchronizer::notifyall(h_lock, THREAD);
- }
- ObjectSynchronizer::fast_exit(h_lock(), pending_list_basic_lock, THREAD);
- if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION;
- }
在一次gc後,Reference對象的pending屬性再也不爲空,讓咱們看看Reference的部分代碼this
首先是pending屬性的說明:spa
- /* 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;
接下來是Reference中的內部類ReferenceHandler,它繼承了Thread,看看run方法的代碼
- 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);
- }
- }
- }
一旦jvm notify了前面提到的鎖,這個線程就被激活並開始執行,做用是將以前jvm賦值過來的pending對象中的WeakReference對象enqueue到指定的隊列中,好比WeakHashMap內部定義的ReferenceQueue屬性
此時map的queue中保存了referent已經被回收的WeakReference隊列,也就是map的Entry對象,當調用size方法時,內部首先調用expungStaleEntries方法清除被回收掉的Entry,代碼以下
- private void expungeStaleEntries() {
- Entry<K,V> e;
- while ( (e = (Entry<K,V>) queue.poll()) != null) {
- int h = e.hash;
- int i = indexFor(h, table.length);
- Entry<K,V> prev = table[i];
- Entry<K,V> p = prev;
- while (p != null) {
- Entry<K,V> next = p.next;
- if (p == e) {
- if (prev == e)
- table[i] = next;
- else
- prev.next = next;
- e.next = null; // Help GC
- e.value = null; // " "
- size--;
- break;
- }
- prev = p;
- p = next;
- }
- }
- }
ok,就這樣map的廢棄Entry被clear,size返回爲0
通過簡單的測試程序發現:
一次gc未必能徹底回收全部的weak ref
weak對象也可能會出如今old generation
參考:
http://weblogs.java.net/blog/2006/05/04/understanding-weak-references
http://stackoverflow.com/questions/154724/when-would-you-use-a-weakhashmap-or-a-weakreference