中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

JDK 源碼閱讀 Reference

2018-09-17    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

Java最初只有普通的強引用,只有對象存在引用,則對象就不會被回收,即使內(nèi)存不足,也是如此,JVM會爆出OOME,也不會去回收存在引用的對象。

如果只提供強引用,我們就很難寫出“這個對象不是很重要,如果內(nèi)存不足GC回收掉也是可以的”這種語義的代碼。Java在1.2版本中完善了引用體系,提供了4中引用類型:強引用,軟引用,弱引用,虛引用。使用這些引用類型,我們不但可以控制垃圾回收器對對象的回收策略,同時還能在對象被回收后得到通知,進行相應(yīng)的后續(xù)操作。

引用與可達(dá)性分類

Java目前有4中引用類型:

  1. 強引用(Strong Reference):普通的的引用類型,new一個對象默認(rèn)得到的引用就是強引用,只要對象存在強引用,就不會被GC。
  2. 軟引用(Soft Reference):相對較弱的引用,垃圾回收器會在內(nèi)存不足時回收弱引用指向的對象。JVM會在拋出OOME前清理所有弱引用指向的對象,如果清理完還是內(nèi)存不足,才會拋出OOME。所以軟引用一般用于實現(xiàn)內(nèi)存敏感緩存。
  3. 弱引用(Weak Reference):更弱的引用類型,垃圾回收器在GC時會回收此對象,也可以用于實現(xiàn)緩存,比如JDK提供的WeakHashMap。
  4. 虛引用(Phantom Reference):一種特殊的引用類型,不能通過虛引用獲取到關(guān)聯(lián)對象,只是用于獲取對象被回收的通知。

相較于傳統(tǒng)的引用計數(shù)算法,Java使用可達(dá)性分析來判斷一個對象是否存活。其基本思路是從GC Root開始向下搜索,如果對象與GC Root之間存在引用鏈,則對象是可達(dá)的。對象的可達(dá)性與引用類型密切相關(guān)。Java有5中類型的可達(dá)性:

  1. 強可達(dá)(Strongly Reachable):如果線程能通過強引用訪問到對象,那么這個對象就是強可達(dá)的。
  2. 軟可達(dá)(Soft Reachable):如果一個對象不是強可達(dá)的,但是可以通過軟引用訪問到,那么這個對象就是軟可達(dá)的
  3. 弱可達(dá)(Weak Reachable):如果一個對象不是強可達(dá)或者軟可達(dá)的,但是可以通過弱引用訪問到,那么這個對象就是弱可達(dá)的。
  4. 虛可達(dá)(Phantom Reachable):如果一個對象不是強可達(dá),軟可達(dá)或者弱可達(dá),并且這個對象已經(jīng)finalize過了,并且有虛引用指向該對象,那么這個對象就是虛可達(dá)的。
  5. 不可達(dá)(Unreachable):如果對象不能通過上述的幾種方式訪問到,則對象是不可達(dá)的,可以被回收。

對象的引用類型與可達(dá)性聽著有點亂,好像是一回事,我們這里實例分析一下:

上面這個例子中,A~D,每個對象只存在一個引用,分別是:A-強引用,B-軟引用,C-弱引用,D-虛引用,所以他們的可達(dá)性為:A-強可達(dá),B-軟可達(dá),C-弱可達(dá),D-虛可達(dá)。因為E沒有存在和GC Root的引用鏈,所以它是不可達(dá)。

在看一個復(fù)雜的例子:

  • A依然只有一個強引用,所以A是強可達(dá)
  • B存在兩個引用,強引用和軟引用,但是B可以通過強引用訪問到,所以B是強可達(dá)
  • C只能通過弱引用訪問到,所以是弱可達(dá)
  • D存在弱引用和虛引用,所以是弱可達(dá)
  • E雖然存在F的強引用,但是GC Root無法訪問到它,所以它依然是不可達(dá)。

同時可以看出,對象的可達(dá)性是會發(fā)生變化的,隨著運行時引用對象的引用類型的變化,可達(dá)性也會發(fā)生變化,可以參考下圖:

Reference總體結(jié)構(gòu)

Reference類是所有引用類型的基類,Java提供了具體引用類型的具體實現(xiàn):

  • SoftReference:軟引用,堆內(nèi)存不足時,垃圾回收器會回收對應(yīng)引用
  • WeakReference:弱引用,每次垃圾回收都會回收其引用
  • PhantomReference:虛引用,對引用無影響,只用于獲取對象被回收的通知
  • FinalReference:Java用于實現(xiàn)finalization的一個內(nèi)部類

因為默認(rèn)的引用就是強引用,所以沒有強引用的Reference實現(xiàn)類。

Reference的核心

Java的多種引用類型實現(xiàn),不是通過擴展語法實現(xiàn)的,而是利用類實現(xiàn)的,Reference類表示一個引用,其核心代碼就是一個成員變量reference

public abstract class Reference<T> {
	private T referent; // 會被GC特殊對待

    // 獲取Reference管理的對象
    public T get() {
        return this.referent;
    }

    // ...
}

如果JVM沒有對這個變量做特殊處理,它依然只是一個普通的強引用,之所以會出現(xiàn)不同的引用類型,是因為JVM垃圾回收器硬編碼識別SoftReference,WeakReferencePhantomReference等這些具體的類,對其reference變量進行特殊對象,才有了不同的引用類型的效果。

上文提到了Reference及其子類有兩大功能:

  1. 實現(xiàn)特定的引用類型
  2. 用戶可以對象被回收后得到通知

第一個功能已經(jīng)解釋過了,第二個功能是如何做到的呢?

一種思路是在新建一個Reference實例是,添加一個回調(diào),當(dāng)java.lang.ref.Reference#referent被回收時,JVM調(diào)用該回調(diào),這種思路比較符合一般的通知模型,但是對于引用與垃圾回收這種底層場景來說,會導(dǎo)致實現(xiàn)復(fù)雜,性能不高的問題,比如需要考慮在什么線程中執(zhí)行這個回調(diào),回調(diào)執(zhí)行阻塞怎么辦等等。

所以Reference使用了一種更加原始的方式來做通知,就是把引用對象被回收的Reference添加到一個隊列中,用戶后續(xù)自己去從隊列中獲取并使用。

理解了設(shè)計后對應(yīng)到代碼上就好理解了,Reference有一個queue成員變量,用于存儲引用對象被回收的Reference實例:

public abstract class Reference<T> {
    // 會被GC特殊對待
	private T referent; 
    // reference被回收后,當(dāng)前Reference實例會被添加到這個隊列中
    volatile ReferenceQueue<? super T> queue;

    // 只傳入reference的構(gòu)造函數(shù),意味著用戶只需要特殊的引用類型,不關(guān)心對象何時被GC
    Reference(T referent) {
        this(referent, null);
    }

    // 傳入referent和ReferenceQueue的構(gòu)造函數(shù),reference被回收后,會添加到queue中
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

    // ...
}

Reference的狀態(tài)

Reference對象是有狀態(tài)的。一共有4中狀態(tài):

  1. Active:新創(chuàng)建的實例的狀態(tài),由垃圾回收器進行處理,如果實例的可達(dá)性處于合適的狀態(tài),垃圾回收器會切換實例的狀態(tài)為Pending或者Inactive。如果Reference注冊了ReferenceQueue,則會切換為Pending,并且Reference會加入pending-Reference鏈表中,如果沒有注冊ReferenceQueue,會切換為Inactive。
  2. Pending:在pending-Reference鏈表中的Reference的狀態(tài),這些Reference等待被加入ReferenceQueue中。
  3. Enqueued:在ReferenceQueue隊列中的Reference的狀態(tài),如果Reference從隊列中移除,會進入Inactive狀態(tài)
  4. Inactive:Reference的最終狀態(tài)

Reference對象圖如下:

除了上文提到的ReferenceQueue,這里出現(xiàn)了一個新的數(shù)據(jù)結(jié)構(gòu):pending-Reference。這個鏈表是用來干什么的呢?

上文提到了,reference引用的對象被回收后,該Reference實例會被添加到ReferenceQueue中,但是這個不是垃圾回收器來做的,這個操作還是有一定邏輯的,如果垃圾回收器還需要執(zhí)行這個操作,會降低其效率。從另外一方面想,Reference實例會被添加到ReferenceQueue中的實效性要求不高,所以也沒必要在回收時立馬加入ReferenceQueue。

所以垃圾回收器做的是一個更輕量級的操作:把Reference添加到pending-Reference鏈表中。Reference對象中有一個pending成員變量,是靜態(tài)變量,它就是這個pending-Reference鏈表的頭結(jié)點。要組成鏈表,還需要一個指針,指向下一個節(jié)點,這個對應(yīng)的是java.lang.ref.Reference#discovered這個成員變量。

可以看一下代碼:

public abstract class Reference<T> {
    // 會被GC特殊對待
	private T referent; 
    // reference被回收后,當(dāng)前Reference實例會被添加到這個隊列中
    volatile ReferenceQueue<? super T> queue; 

    // 全局唯一的pending-Reference列表
    private static Reference<Object> pending = null;

    // Reference為Active:由垃圾回收器管理的已發(fā)現(xiàn)的引用列表(這個不在本文討論訪問內(nèi))
    // Reference為Pending:在pending列表中的下一個元素,如果沒有為null
    // 其他狀態(tài):NULL
    transient private Reference<T> discovered;  /* used by VM */
    // ...
}

 

ReferenceHandler線程

通過上文的討論,我們知道一個Reference實例化后狀態(tài)為Active,其引用的對象被回收后,垃圾回收器將其加入到pending-Reference鏈表,等待加入ReferenceQueue。這個過程是如何實現(xiàn)的呢?

這個過程不能對垃圾回收器產(chǎn)生影響,所以不能在垃圾回收線程中執(zhí)行,也就需要一個獨立的線程來負(fù)責(zé)。這個線程就是ReferenceHandler,它定義在Reference類中:

// 用于控制垃圾回收器操作與Pending狀態(tài)的Reference入隊操作不沖突執(zhí)行的全局鎖
// 垃圾回收器開始一輪垃圾回收前要獲取此鎖
// 所以所有占用這個鎖的代碼必須盡快完成,不能生成新對象,也不能調(diào)用用戶代碼
static private class Lock { };
private static Lock lock = new Lock();

private static class ReferenceHandler extends Thread {

    ReferenceHandler(ThreadGroup g, String name) {
        super(g, name);
    }

    public void run() {
        // 這個線程一直執(zhí)行
        for (;;) {
            Reference<Object> r;
            // 獲取鎖,避免與垃圾回收器同時操作
            synchronized (lock) {
                // 判斷pending-Reference鏈表是否有數(shù)據(jù)
                if (pending != null) {
                    // 如果有Pending Reference,從列表中取出
                    r = pending;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // 如果沒有Pending Reference,調(diào)用wait等待
                    // 
                    // wait等待鎖,是可能拋出OOME的,
                    // 因為可能發(fā)生InterruptedException異常,然后就需要實例化這個異常對象,
                    // 如果此時內(nèi)存不足,就可能拋出OOME,所以這里需要捕獲OutOfMemoryError,
                    // 避免因為OOME而導(dǎo)致ReferenceHandler進程靜默退出
                    try {
                        try {
                            lock.wait();
                        } catch (OutOfMemoryError x) { }
                    } catch (InterruptedException x) { }
                    continue;
                }
            }

            // 如果Reference是Cleaner,調(diào)用其clean方法
            // 這與Cleaner機制有關(guān)系,不在此文的討論訪問
            if (r instanceof Cleaner) {
                ((Cleaner)r).clean();
                continue;
            }

            // 把Reference添加到關(guān)聯(lián)的ReferenceQueue中
            // 如果Reference構(gòu)造時沒有關(guān)聯(lián)ReferenceQueue,會關(guān)聯(lián)ReferenceQueue.NULL,這里就不會進行入隊操作了
            ReferenceQueue<Object> q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }
}

ReferenceHandler線程是在Reference的static塊中啟動的:

static {
    // 獲取system ThreadGroup
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    Thread handler = new ReferenceHandler(tg, "Reference Handler");

    // ReferenceHandler線程有最高優(yōu)先級
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();
}

綜上,ReferenceHandler是一個最高優(yōu)先級的線程,其邏輯是從Pending-Reference鏈表中取出Reference,添加到其關(guān)聯(lián)的Reference-Queue中。

ReferenceQueue

Reference-Queue也是一個鏈表:

public class ReferenceQueue<T> {
    private volatile Reference<? extends T> head = null;
    // ...
}
// ReferenceQueue中的這個鎖用于保護鏈表隊列在多線程環(huán)境下的正確性
static private class Lock { };
private Lock lock = new Lock();

boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    synchronized (lock) {
		// 判斷Reference是否需要入隊
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;

        // Reference入隊后,其queue變量設(shè)置為ENQUEUED
        r.queue = ENQUEUED;
        // Reference的next變量指向ReferenceQueue中下一個元素
        r.next = (head == null) ? r : head;
        head = r;
        queueLength++;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}

 

通過上面的代碼,可以知道java.lang.ref.Reference#next的用途了:

public abstract class Reference<T> {
    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   指向ReferenceQueue中的下一個元素,如果沒有,指向this
     *    Inactive:   this
     */
    Reference next;

    // ...
}

總結(jié)

一個使用Reference+ReferenceQueue的完整流程如下:

參考資料

  • Java Reference詳解 – robin-yao的個人頁面 – 開源中國
  • Internals of Java Reference Object
  • java.lang.ref (Java Platform SE 7 )
  • Java Reference Objects
  • Java核心技術(shù)36講 第4講

標(biāo)簽: 代碼 搜索

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:Java 中處理異常的 9 個最佳實踐

下一篇:MySQL 8.0 中統(tǒng)計信息直方圖的嘗試