Weak Reference
http://rockybalboa.blog.51cto.com/1010693/813161
数年前にJavaの4つの引用のタイプを見ましたが、単純に異なるタイプの作用を知っていました.しかし、その実現原理についてはまだ分かりませんでした.本稿ではjdk、openjdk 6の一部のソースコード分析を結び付けてみました.
実例分析
WeakhashMapの処理手順を例に挙げてweak referenceのライフサイクルを紹介します.まず、WeakhashMapのputメソッドを呼び出して、対象をMapに入れます.WeakhashMapのEntryはWeak Referenceを継承しました.
第一歩は、keyが到達していないstrong refで、一つのweak referenceだけが存在するreferent変数は依然としてkeyを指しています.GCをトリガする時、openjdk 6のparNewを例にして、jvmはyoung generation gcの時にReferenceオブジェクト内の静的なグローバルロックを取得しようとします.
まず、pending属性の説明です.
この時、mapのqueueには、referentが回収されたWeakReferenceのキュー、つまりmapのEntryオブジェクトが保存されています.sizeメソッドを呼び出した場合、内部はまずexpund Stree Enttriesメソッドを呼び出して、回収されたEntryをクリアします.コードは以下の通りです.
簡単なテスト手順で発見されました.
一回のgcはすべてのweak refを完全に回収できるとは限らない.
weakオブジェクトもold generationに現れるかもしれません.
数年前にJavaの4つの引用のタイプを見ましたが、単純に異なるタイプの作用を知っていました.しかし、その実現原理についてはまだ分かりませんでした.本稿ではjdk、openjdk 6の一部のソースコード分析を結び付けてみました.
実例分析
WeakhashMapの処理手順を例に挙げてweak referenceのライフサイクルを紹介します.まず、WeakhashMapのputメソッドを呼び出して、対象をMapに入れます.WeakhashMapのEntryはWeak Referenceを継承しました.
- 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の部分コードです.
- 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のコンストラクターを呼びます.
- 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を試みます.そしてWeakhashMapのsizeメソッドを呼び出して集合の個数を返します.ほとんどの場合は0です.この過程で何が起きましたか?第一歩は、keyが到達していないstrong refで、一つのweak referenceだけが存在するreferent変数は依然としてkeyを指しています.GCをトリガする時、openjdk 6の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();
openjdk 6のソースコードの一部は、完全なコードはinstance RefKlass.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属性に値を付けてロックを解除します.参考方法:
- 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の部分コードを見てみましょう.まず、pending属性の説明です.
- /* 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オブジェクトのうち、Weak Referenceオブジェクトのenqueueを指定されたキューの中に、例えばWeakhashMap内部で定義されているReferenceQue属性です.この時、mapのqueueには、referentが回収されたWeakReferenceのキュー、つまりmapのEntryオブジェクトが保存されています.sizeメソッドを呼び出した場合、内部はまずexpund Stree Enttriesメソッドを呼び出して、回収された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に現れるかもしれません.