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

JDK 源碼閱讀 Reference

2018-09-17    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

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

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

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

Java目前有4中引用類型:

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

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

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

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

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

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

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

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

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

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

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

因?yàn)槟J(rèn)的引用就是強(qiáng)引用,所以沒有強(qiáng)引用的Reference實(shí)現(xiàn)類。

Reference的核心

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

public abstract class Reference<T> {
	private T referent; // 會(huì)被GC特殊對(duì)待

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

    // ...
}

如果JVM沒有對(duì)這個(gè)變量做特殊處理,它依然只是一個(gè)普通的強(qiáng)引用,之所以會(huì)出現(xiàn)不同的引用類型,是因?yàn)镴VM垃圾回收器硬編碼識(shí)別SoftReference,WeakReference,PhantomReference等這些具體的類,對(duì)其reference變量進(jìn)行特殊對(duì)象,才有了不同的引用類型的效果。

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

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

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

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

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

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

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

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

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

    // ...
}

Reference的狀態(tài)

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

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

Reference對(duì)象圖如下:

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

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

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

可以看一下代碼:

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

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

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

 

ReferenceHandler線程

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

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

// 用于控制垃圾回收器操作與Pending狀態(tài)的Reference入隊(duì)操作不沖突執(zhí)行的全局鎖
// 垃圾回收器開始一輪垃圾回收前要獲取此鎖
// 所以所有占用這個(gè)鎖的代碼必須盡快完成,不能生成新對(duì)象,也不能調(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() {
        // 這個(gè)線程一直執(zhí)行
        for (;;) {
            Reference<Object> r;
            // 獲取鎖,避免與垃圾回收器同時(shí)操作
            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的,
                    // 因?yàn)榭赡馨l(fā)生InterruptedException異常,然后就需要實(shí)例化這個(gè)異常對(duì)象,
                    // 如果此時(shí)內(nèi)存不足,就可能拋出OOME,所以這里需要捕獲OutOfMemoryError,
                    // 避免因?yàn)镺OME而導(dǎo)致ReferenceHandler進(jìn)程靜默退出
                    try {
                        try {
                            lock.wait();
                        } catch (OutOfMemoryError x) { }
                    } catch (InterruptedException x) { }
                    continue;
                }
            }

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

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

ReferenceHandler線程是在Reference的static塊中啟動(dòng)的:

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)先級(jí)
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();
}

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

ReferenceQueue

Reference-Queue也是一個(gè)鏈表:

public class ReferenceQueue<T> {
    private volatile Reference<? extends T> head = null;
    // ...
}
// ReferenceQueue中的這個(gè)鎖用于保護(hù)鏈表隊(duì)列在多線程環(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是否需要入隊(duì)
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;

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

 

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

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

    // ...
}

總結(jié)

一個(gè)使用Reference+ReferenceQueue的完整流程如下:

參考資料

  • Java Reference詳解 – robin-yao的個(gè)人頁(yè)面 – 開源中國(guó)
  • 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),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Java 中處理異常的 9 個(gè)最佳實(shí)踐

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