Java Reference 源碼分析


@(Java)[Reference]

Java Reference 源碼分析

  Reference對象封裝了其它對象的引用,可以和普通的對象一樣操作,在一定的限制條件下,支持和垃圾收集器的交互。即可以使用Reference對象來引用其它對象,但是最后還是會被垃圾收集器回收。程序有時候也需要在對象回收后被通知,以告知對象的可達性發生變更。
  Java提供了四種不同類型的引用,引用級別從高到低分別為FinalReference,SoftReference,WeakReference,PhantomReference。其中FinalReference不對外提供使用。每種類型對應着不同級別的可達性。

簡介

強引用FinalReference

強引用指的是,程序中有直接可達的引用,而不需要通過任何引用對象,如Object obj = new Object();中,obj為強引用。

軟引用SoftReference

軟引用,非強引用,但是可以通過軟引用對象來訪問。軟引用的對象,只有在內存不足的時候(拋出OOM異常前),垃圾收集器會決定回收該軟引用所指向的對象。軟引用通常用於實現內存敏感的緩存。

SoftReference<Object> softRef = new SoftReference<Object>(new Object());

弱引用WeakReference

弱引用,非強引用和軟引用,但是可以通過弱引用對象來訪問。弱引用的對象,不管內存是否足夠,只要被垃圾收集器發現,該引用的對象就會被回收。實際的應用見WeakHashMap等。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object());

虛引用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);

源碼分析

ReferenceReferenceQueue

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作為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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM