Weak Reference


http://rockybalboa.blog.51cto.com/1010693/813161
数年前にJavaの4つの引用のタイプを見ましたが、単純に異なるタイプの作用を知っていました.しかし、その実現原理についてはまだ分かりませんでした.本稿ではjdk、openjdk 6の一部のソースコード分析を結び付けてみました.
 実例分析
WeakhashMapの処理手順を例に挙げてweak referenceのライフサイクルを紹介します.まず、WeakhashMapのputメソッドを呼び出して、対象をMapに入れます.WeakhashMapのEntryはWeak Referenceを継承しました.
 

  
  
  
  
  1. private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> { 
  2.         private V value; 
  3.         private final int hash; 
  4.         private Entry<K,V> next; 
下はputの部分コードです.

  
  
  
  
  1. Entry<K,V> e = tab[i]; 
  2.         tab[i] = new Entry<K,V>(k, value, queue, h, e); 
  3.         if (++size >= threshold) 
  4.             resize(tab.length * 2); 
  5.         return null; 
  6.     } 
new Entryがreference queueをコンストラクタに伝達しました.このコンストラクターは最終的にReferenceのコンストラクターを呼びます.

  
  
  
  
  1. Reference(T referent, ReferenceQueue<? super T> queue) { 
  2.     this.referent = referent; 
  3.     this.queue = (queue == null) ? ReferenceQueue.NULL : queue; 
  4.     } 
referentは私達の前に伝わるhashmapのkeyオブジェクトで、queueの役割はreferentが回収されたweak referenceを読むためです.生産者は後で紹介します.この時WeakhashMapにはもう一つの対象が存在します.まずkey対象のstrong refを空にしてトリガgcを試みます.そしてWeakhashMapのsizeメソッドを呼び出して集合の個数を返します.ほとんどの場合は0です.この過程で何が起きましたか?
第一歩は、keyが到達していないstrong refで、一つのweak referenceだけが存在するreferent変数は依然としてkeyを指しています.GCをトリガする時、openjdk 6のparNewを例にして、jvmはyoung generation gcの時にReferenceオブジェクト内の静的なグローバルロックを取得しようとします.
 

  
  
  
  
  1. /* Object used to synchronize with the garbage collector.  The collector 
  2.      * must acquire this lock at the beginning of each collection cycle.  It is 
  3.      * therefore critical that any code holding this lock complete as quickly 
  4.      * as possible, allocate no new objects, and avoid calling user code. 
  5.      */ 
  6.     static private class Lock { }; 
  7.     private static Lock lock = new Lock(); 
openjdk 6のソースコードの一部は、完全なコードはinstance RefKlass.cppファイルを参照してください.

  
  
  
  
  1. void instanceRefKlass::acquire_pending_list_lock(BasicLock *pending_list_basic_lock) { 
  2.   // we may enter this with pending exception set 
  3.   PRESERVE_EXCEPTION_MARK;  // exceptions are never thrown, needed for TRAPS argument 
  4.   Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock()); 
  5.   ObjectSynchronizer::fast_enter(h_lock, pending_list_basic_lock, false, THREAD); 
  6.   assert(ObjectSynchronizer::current_thread_holds_lock( 
  7.            JavaThread::current(), h_lock), 
  8.          "Locking should have succeeded"); 
  9.   if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION; 
 ここのコードは、parNew gcで実行されます.目的はグローバルロックを取得することです.gcが完了すると、jvmはkeyが回収されたweak referenceをqueueとして、Referenceのpending属性に値を付けてロックを解除します.参考方法:

  
  
  
  
  1. void instanceRefKlass::release_and_notify_pending_list_lock( 
  2.   BasicLock *pending_list_basic_lock) { 
  3.   // we may enter this with pending exception set 
  4.   PRESERVE_EXCEPTION_MARK;  // exceptions are never thrown, needed for TRAPS argument 
  5.   // 
  6.   Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock()); 
  7.   assert(ObjectSynchronizer::current_thread_holds_lock( 
  8.            JavaThread::current(), h_lock), 
  9.          "Lock should be held"); 
  10.   // Notify waiters on pending lists lock if there is any reference. 
  11.   if (java_lang_ref_Reference::pending_list() != NULL) { 
  12.     ObjectSynchronizer::notifyall(h_lock, THREAD); 
  13.   } 
  14.   ObjectSynchronizer::fast_exit(h_lock(), pending_list_basic_lock, THREAD); 
  15.   if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION; 
一回のgcの後で、Referenceの対象のpending属性はもう空ではなくて、Referenceの部分コードを見てみましょう.
まず、pending属性の説明です.

  
  
  
  
  1. /* List of References waiting to be enqueued.  The collector adds 
  2.  * References to this list, while the Reference-handler thread removes 
  3.  * them.  This list is protected by the above lock object. 
  4.  */ 
  5. private static Reference pending = null
次にReferenceの内部クラスReferenceHandlerです.Threadを継承しています.runメソッドのコードを見てください.

  
  
  
  
  1. public void run() { 
  2.         for (;;) { 
  3.  
  4.         Reference r; 
  5.         synchronized (lock) { 
  6.             if (pending != null) { 
  7.             r = pending; 
  8.             Reference rn = r.next; 
  9.             pending = (rn == r) ? null : rn; 
  10.             r.next = r; 
  11.             } else { 
  12.             try { 
  13.                 lock.wait(); 
  14.             } catch (InterruptedException x) { } 
  15.             continue
  16.             } 
  17.         } 
  18.  
  19.         // Fast path for cleaners 
  20.         if (r instanceof Cleaner) { 
  21.             ((Cleaner)r).clean(); 
  22.             continue
  23.         } 
  24.  
  25.         ReferenceQueue q = r.queue; 
  26.         if (q != ReferenceQueue.NULL) q.enqueue(r); 
  27.         } 
  28.     } 
  29.     } 
jvm notifyが前に述べたロックをすると、このスレッドはアクティブ化されて実行を開始します.これは先ほどjvmに割り当てられたpendingオブジェクトのうち、Weak Referenceオブジェクトのenqueueを指定されたキューの中に、例えばWeakhashMap内部で定義されているReferenceQue属性です.
この時、mapのqueueには、referentが回収されたWeakReferenceのキュー、つまりmapのEntryオブジェクトが保存されています.sizeメソッドを呼び出した場合、内部はまずexpund Stree Enttriesメソッドを呼び出して、回収されたEntryをクリアします.コードは以下の通りです.
 

  
  
  
  
  1. private void expungeStaleEntries() { 
  2.     Entry<K,V> e; 
  3.         while ( (e = (Entry<K,V>) queue.poll()) != null) { 
  4.             int h = e.hash; 
  5.             int i = indexFor(h, table.length); 
  6.  
  7.             Entry<K,V> prev = table[i]; 
  8.             Entry<K,V> p = prev; 
  9.             while (p != null) { 
  10.                 Entry<K,V> next = p.next; 
  11.                 if (p == e) { 
  12.                     if (prev == e) 
  13.                         table[i] = next; 
  14.                     else 
  15.                         prev.next = next; 
  16.                     e.next = null;  // Help GC 
  17.                     e.value = null//  "   " 
  18.                     size--; 
  19.                     break
  20.                 } 
  21.                 prev = p; 
  22.                 p = next; 
  23.             } 
  24.         } 
  25.     } 
ok、そのままmapの廃棄Entryはclear、sizeは0に戻ります.
簡単なテスト手順で発見されました.
一回のgcはすべてのweak refを完全に回収できるとは限らない.
weakオブジェクトもold generationに現れるかもしれません.