欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java編程Reference核心原理示例源碼分析

 更新時(shí)間:2022年01月24日 09:37:57   作者:葉易_公眾號(hào)洞悉源碼  
這篇文章主要為大家介紹了java編程Reference的核心原理以及示例源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪

帶著問題,看源碼針對(duì)性會(huì)更強(qiáng)一點(diǎn)、印象會(huì)更深刻、并且效果也會(huì)更好。所以我先賣個(gè)關(guān)子,提兩個(gè)問題(沒準(zhǔn)下次跳槽時(shí)就被問到)。

  • 我們可以用ByteBuffer的allocateDirect方法,申請(qǐng)一塊堆外內(nèi)存創(chuàng)建一個(gè)DirectByteBuffer對(duì)象,然后利用它去操作堆外內(nèi)存。這些申請(qǐng)完的堆外內(nèi)存,我們可以回收嗎?可以的話是通過什么樣的機(jī)制回收的?
  • 大家應(yīng)該都知道WeakHashMap可以用來實(shí)現(xiàn)內(nèi)存相對(duì)敏感的本地緩存,為什么WeakHashMap合適這種業(yè)務(wù)場(chǎng)景,其內(nèi)部實(shí)現(xiàn)會(huì)做什么特殊處理呢?

GC可到達(dá)性與JDK中Reference類型

上面提到的兩個(gè)問題,其答案都在JDK的Reference里面。JDK早期版本中并沒有Reference相關(guān)的類,這導(dǎo)致對(duì)象被GC回收后如果想做一些額外的清理工作(比如socket、堆外內(nèi)存等)是無法實(shí)現(xiàn)的,同樣如果想要根據(jù)堆內(nèi)存的實(shí)際使用情況決定要不要去清理一些內(nèi)存敏感的對(duì)象也是法實(shí)現(xiàn)的。

為此JDK1.2中引入的Reference相關(guān)的類,即今天要介紹的Reference、SoftReference、WeakReference、PhantomReference,還有與之相關(guān)的Cleaner、ReferenceQueue、ReferenceHandler等。與Reference相關(guān)核心類基本都在java.lang.ref包下面。其類關(guān)系如下:

其中,SoftReference代表軟引用對(duì)象,垃圾回收器會(huì)根據(jù)內(nèi)存需求酌情回收軟引用指向的對(duì)象。普通的GC并不會(huì)回收軟引用,只有在即將OOM的時(shí)候(也就是最后一次Full GC)如果被引用的對(duì)象只有SoftReference指向的引用,才會(huì)回收。WeakReference代表弱引用對(duì)象,當(dāng)發(fā)生GC時(shí),如果被引用的對(duì)象只有WeakReference指向的引用,就會(huì)被回收。PhantomReference代表虛引用對(duì)象(也有叫幻象引用的,個(gè)人認(rèn)為還是虛引用更加貼切),其是一種特殊的引用類型,不能通過虛引用獲取到其關(guān)聯(lián)的對(duì)象,但當(dāng)GC時(shí)如果其引用的對(duì)象被回收,這個(gè)事件程序可以感知,這樣我們可以做相應(yīng)的處理。最后就是最常見強(qiáng)引用對(duì)象,也就是通常我們new出來的對(duì)象。在繼續(xù)介紹Reference相關(guān)類的源碼前,先來簡(jiǎn)單的看一下GC如何決定一個(gè)對(duì)象是否可被回收。其基本思路是從GC Root開始向下搜索,如果對(duì)象與GC Root之間存在引用鏈,則對(duì)象是可達(dá)的,GC會(huì)根據(jù)是否可到達(dá)與可到達(dá)性決定對(duì)象是否可以被回收。而對(duì)象的可達(dá)性與引用類型密切相關(guān),對(duì)象的可到達(dá)性可分為5種。

  • 強(qiáng)可到達(dá),如果從GC Root搜索后,發(fā)現(xiàn)對(duì)象與GC Root之間存在強(qiáng)引用鏈則為強(qiáng)可到達(dá)。強(qiáng)引用鏈即有強(qiáng)引用對(duì)象,引用了該對(duì)象。
  • 軟可到達(dá),如果從GC Root搜索后,發(fā)現(xiàn)對(duì)象與GC Root之間不存在強(qiáng)引用鏈,但存在軟引用鏈,則為軟可到達(dá)。軟引用鏈即有軟引用對(duì)象,引用了該對(duì)象。
  • 弱可到達(dá),如果從GC Root搜索后,發(fā)現(xiàn)對(duì)象與GC Root之間不存在強(qiáng)引用鏈與軟引用鏈,但有弱引用鏈,則為弱可到達(dá)。弱引用鏈即有弱引用對(duì)象,引用了該對(duì)象。
  • 虛可到達(dá),如果從GC Root搜索后,發(fā)現(xiàn)對(duì)象與GC Root之間只存在虛引用鏈則為虛可到達(dá)。虛引用鏈即有虛引用對(duì)象,引用了該對(duì)象。
  • 不可達(dá),如果從GC Root搜索后,找不到對(duì)象與GC Root之間的引用鏈,則為不可到達(dá)。
    看一個(gè)簡(jiǎn)單的列子:

ObjectA為強(qiáng)可到達(dá),ObjectB也為強(qiáng)可到達(dá),雖然ObjectB對(duì)象被SoftReference ObjcetE 引用但由于其還被ObjectA引用所以為強(qiáng)可到達(dá);而ObjectC和ObjectD為弱引用達(dá)到,雖然ObjectD對(duì)象被PhantomReference ObjcetG引用但由于其還被ObjectC引用,而ObjectC又為弱引用達(dá)到,所以O(shè)bjectD為弱引用達(dá)到;而ObjectH與ObjectI是不可到達(dá)。引用鏈的強(qiáng)弱有關(guān)系依次是 強(qiáng)引用 > 軟引用 > 弱引用 > 虛引用,如果有更強(qiáng)的引用關(guān)系存在,那么引用鏈到達(dá)性,將由更強(qiáng)的引用有關(guān)系決定。

Reference核心處理流程

JVM在GC時(shí)如果當(dāng)前對(duì)象只被Reference對(duì)象引用,JVM會(huì)根據(jù)Reference具體類型與堆內(nèi)存的使用情況決定是否把對(duì)應(yīng)的Reference對(duì)象加入到一個(gè)由Reference構(gòu)成的pending鏈表上,如果能加入pending鏈表JVM同時(shí)會(huì)通知ReferenceHandler線程進(jìn)行處理。ReferenceHandler線程是在Reference類被初始化時(shí)調(diào)用的,其是一個(gè)守護(hù)進(jìn)程并且擁有最高的優(yōu)先級(jí)。Reference類靜態(tài)初始化塊代碼如下:

static {
    //省略部分代碼...
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();
    //省略部分代碼...
}

而ReferenceHandler線程內(nèi)部的run方法會(huì)不斷地從Reference構(gòu)成的pending鏈表上獲取Reference對(duì)象,如果能獲取則根據(jù)Reference的具體類型進(jìn)行不同的處理,不能則調(diào)用wait方法等待GC回收對(duì)象處理pending鏈表的通知。ReferenceHandler線程run方法源碼:

public void run() {
    //死循環(huán),線程啟動(dòng)后會(huì)一直運(yùn)行
    while (true) {
        tryHandlePending(true);
    }
}

run內(nèi)部調(diào)用的tryHandlePending源碼:

static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            if (pending != null) {
                r = pending;
                //instanceof 可能會(huì)拋出OOME,所以在將r從pending鏈上斷開前,做這個(gè)處理
                c = r instanceof Cleaner ? (Cleaner) r : null;
                //將將r從pending鏈上斷開
                pending = r.discovered;
                r.discovered = null;
            } else {
                //等待CG后的通知
                if (waitForNotify) {
                    lock.wait();
                }
                //重試
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {
        //當(dāng)拋出OOME時(shí),放棄CPU的運(yùn)行時(shí)間,這樣有希望收回一些存活的引用并且GC能回收部分空間。同時(shí)能避免頻繁地自旋重試,導(dǎo)致連續(xù)的OOME異常
        Thread.yield();
        //重試
        return true;
    } catch (InterruptedException x) {
         //重試
        return true;
    }
    //如果是Cleaner類型的Reference調(diào)用其clean方法并退出
    if (c != null) {
        c.clean();
        return true;
    }
    ReferenceQueue<? super Object> q = r.queue;
    //如果Reference有注冊(cè)ReferenceQueue,則處理pending指向的Reference結(jié)點(diǎn)將其加入ReferenceQueue中
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;
}

上面tryHandlePending方法中比較重要的點(diǎn)是c.clean()與q.enqueue®,這個(gè)是文章最開始提到的兩個(gè)問題答案的入口。Cleaner的clean方法用于完成清理工作,而ReferenceQueue是將被回收對(duì)象加入到對(duì)應(yīng)的Reference列隊(duì)中,等待其他線程的后繼處理。更具體地關(guān)于Cleaner與ReferenceQueue后面會(huì)再詳細(xì)說明。Reference的核心處理流程可總結(jié)如下:

對(duì)Reference的核心處理流程有整體了解后,再來回過頭細(xì)看一下Reference類的源碼。

/* Reference實(shí)例有四種內(nèi)部的狀態(tài)
 * Active: 新創(chuàng)建Reference的實(shí)例其狀態(tài)為Active。當(dāng)GC檢測(cè)到Reference引用的referent可達(dá)到狀態(tài)發(fā)生改變時(shí),
 * 為改變Reference的狀態(tài)為Pending或Inactive。這個(gè)取決于創(chuàng)建Reference實(shí)例時(shí)是否注冊(cè)過ReferenceQueue。
 * 注冊(cè)過其狀態(tài)會(huì)轉(zhuǎn)換為Pending,同時(shí)GC會(huì)將其加入pending-Reference鏈表中,否則為轉(zhuǎn)換為Inactive狀態(tài)。
 * Pending: 代表Reference是pending-Reference鏈表的成員,等待ReferenceHandler線程調(diào)用Cleaner#clean
 * 或ReferenceQueue#enqueue操作。未注冊(cè)過ReferenceQueue的實(shí)例不會(huì)達(dá)到這個(gè)狀態(tài)
 * Enqueued: Reference實(shí)例成為其被創(chuàng)建時(shí)注冊(cè)過的ReferenceQueue的成員,代表已入隊(duì)列。當(dāng)其從ReferenceQueue
 * 中移除后,其狀態(tài)會(huì)變?yōu)镮nactive。
 * Inactive: 什么也不會(huì)做,一旦處理該狀態(tài),就不可再轉(zhuǎn)換。
 * 不同狀態(tài)時(shí),Reference對(duì)應(yīng)的queue與成員next變量值(next可理解為ReferenceQueue中的下個(gè)結(jié)點(diǎn)的引用)如下:
 * Active: queue為Reference實(shí)例被創(chuàng)建時(shí)注冊(cè)的ReferenceQueue,如果沒注冊(cè)為Null。此時(shí),next為null,
 * Reference實(shí)例與queue真正產(chǎn)生關(guān)系。
 * Pending: queue為Reference實(shí)例被創(chuàng)建時(shí)注冊(cè)的ReferenceQueue。next為當(dāng)前實(shí)例本身。
 * Enqueued: queue為ReferenceQueue.ENQUEUED代表當(dāng)前實(shí)例已入隊(duì)列。next為queue中的下一實(shí)列結(jié)點(diǎn),
 * 如果是queue尾部則為當(dāng)前實(shí)例本身
 * Inactive: queue為ReferenceQueue.NULL,當(dāng)前實(shí)例已從queue中移除與queue無關(guān)聯(lián)。next為當(dāng)前實(shí)例本身。
 */
public abstract class Reference<T> {
// Reference 引用的對(duì)象
private T referent;
/* Reference注冊(cè)的queue用于ReferenceHandler線程入隊(duì)列處理與用戶線程取Reference處理。
 * 其取值會(huì)根據(jù)Reference不同狀態(tài)發(fā)生改變,具體取值見上面的分析
 */
volatile ReferenceQueue<? super T> queue;
// 可理解為注冊(cè)的queue中的下一個(gè)結(jié)點(diǎn)的引用。其取值會(huì)根據(jù)Reference不同狀態(tài)發(fā)生改變,具體取值見上面的分析
volatile Reference next;
/* 其由VM維護(hù),取值會(huì)根據(jù)Reference不同狀態(tài)發(fā)生改變,
 * 狀態(tài)為active時(shí),代表由GC維護(hù)的discovered-Reference鏈表的下個(gè)節(jié)點(diǎn),如果是尾部則為當(dāng)前實(shí)例本身
 * 狀態(tài)為pending時(shí),代表pending-Reference的下個(gè)節(jié)點(diǎn)的引用。否則為null
 */
transient private Reference<T> discovered;
/* pending-Reference 鏈表頭指針,GC回收referent后會(huì)將Reference加pending-Reference鏈表。
 * 同時(shí)ReferenceHandler線程會(huì)獲取pending指針,不為空時(shí)Cleaner.clean()或入列queue。
 * pending-Reference會(huì)采用discovered引用接鏈表的下個(gè)節(jié)點(diǎn)。
 */
private static Reference<Object> pending = null;
// 可理解為注冊(cè)的queue中的下一個(gè)結(jié)點(diǎn)的引用。其取值會(huì)根據(jù)Reference不同狀態(tài)發(fā)生改變,具體取值見上面的分析
volatile Reference next;
//用于CG同步Reference成員變量值的對(duì)象。
static private class Lock { }
private static Lock lock = new Lock();
//省略部分代碼...
}

上面解釋了Reference中的主要成員的作用,其中比較重要是Reference內(nèi)部維護(hù)的不同狀態(tài),其狀態(tài)不同成員變量queue、pending、discovered、next的取值都會(huì)發(fā)生變化。Reference的主要方法如下:

//構(gòu)造函數(shù),指定引用的對(duì)象referent
Reference(T referent) {
    this(referent, null);
}
//構(gòu)造函數(shù),指定引用的對(duì)象referent與注冊(cè)的queue
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
//獲取引用的對(duì)象referent
public T get() {
    return this.referent;
}
//將當(dāng)前對(duì)象加入創(chuàng)建時(shí)注冊(cè)的queue中
public boolean enqueue() {
    return this.queue.enqueue(this);
}

ReferenecQueue與Cleaner源碼分析

先來看下ReferenceQueue的主要成員變量的含義。

//代表Reference的queue為null。Null為ReferenceQueue子類
static ReferenceQueue<Object> NULL = new Null<>();
//代表Reference已加入當(dāng)前ReferenceQueue中。
static ReferenceQueue<Object> ENQUEUED = new Null<>();
//用于同步的對(duì)象
private Lock lock = new Lock();
//當(dāng)前ReferenceQueue中的頭節(jié)點(diǎn)
private volatile Reference<? extends T> head = null;
//ReferenceQueue的長(zhǎng)度
private long queueLength = 0;

ReferenceQueue中比較重要的方法為enqueue、poll、remove方法。

//入列隊(duì)enqueue方法,只被Reference類調(diào)用,也就是上面分析中ReferenceHandler線程為調(diào)用
boolean enqueue(Reference<? extends T> r) {
	//獲取同步對(duì)象lock對(duì)應(yīng)的監(jiān)視器對(duì)象
    synchronized (lock) {
        //獲取r關(guān)聯(lián)的ReferenceQueue,如果創(chuàng)建r時(shí)未注冊(cè)ReferenceQueue則為NULL,同樣如果r已從ReferenceQueue中移除其也為null
        ReferenceQueue<?> queue = r.queue;
        //判斷queue是否為NULL 或者 r已加入ReferenceQueue中,是的話則入隊(duì)列失敗
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        //設(shè)置r的queue為已入隊(duì)列
        r.queue = ENQUEUED;
        //如果ReferenceQueue頭節(jié)點(diǎn)為null則r的next節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn),否則指向頭節(jié)點(diǎn)
        r.next = (head == null) ? r : head;
        //更新ReferenceQueue頭節(jié)點(diǎn)
        head = r;
        //列隊(duì)長(zhǎng)度加1
        queueLength++;
        //為FinalReference類型引用增加FinalRefCount數(shù)量
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        //通知remove操作隊(duì)列有節(jié)點(diǎn)
        lock.notifyAll();
        return true;
    }
}

poll方法源碼相對(duì)簡(jiǎn)單,其就是從ReferenceQueue的頭節(jié)點(diǎn)獲取Reference。

public Reference<? extends T> poll() {
    //頭結(jié)點(diǎn)為null直接返回,代表Reference還沒有加入ReferenceQueue中
    if (head == null)
        return null;
    //獲取同步對(duì)象lock對(duì)應(yīng)的監(jiān)視器對(duì)象
    synchronized (lock) {
        return reallyPoll();
    }
}
//從隊(duì)列中真正poll元素的方法
private Reference<? extends T> reallyPoll() {
    Reference<? extends T> r = head;
    //double check 頭節(jié)點(diǎn)不為null
    if (r != null) {
    	//保存頭節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)引用
        Reference<? extends T> rn = r.next;
        //更新queue頭節(jié)點(diǎn)引用
        head = (rn == r) ? null : rn;
        //更新Reference的queue值,代表r已從隊(duì)列中移除
		r.queue = NULL;
		//更新Reference的next為其本身
        r.next = r;
        queueLength--;
        //為FinalReference節(jié)點(diǎn)FinalRefCount數(shù)量減1
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(-1);
        }
        //返回獲取的節(jié)點(diǎn)
        return r;
    }
    return null;
}

remove方法的源碼如下:

public Reference<? extends T> remove(long timeout) throws IllegalArgumentException, InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("Negative timeout value");
    }
    //獲取同步對(duì)象lock對(duì)應(yīng)的監(jiān)視器對(duì)象
    synchronized (lock) {
    	//獲取隊(duì)列頭節(jié)點(diǎn)指向的Reference
        Reference<? extends T> r = reallyPoll();
        //獲取到返回
        if (r != null) return r;
        long start = (timeout == 0) ? 0 : System.nanoTime();
        //在timeout時(shí)間內(nèi)嘗試重試獲取
        for (;;) {
        	//等待隊(duì)列上有結(jié)點(diǎn)通知
            lock.wait(timeout);
            //獲取隊(duì)列中的頭節(jié)點(diǎn)指向的Reference
            r = reallyPoll();
            //獲取到返回
            if (r != null) return r;
            if (timeout != 0) {
                long end = System.nanoTime();
                timeout -= (end - start) / 1000_000;
                //已超時(shí)但還沒有獲取到隊(duì)列中的頭節(jié)點(diǎn)指向的Reference返回null
                if (timeout <= 0) return null;
                start = end;
            }
        }
    }
}

簡(jiǎn)單的分析完ReferenceQueue的源碼后,再來整體回顧一下Reference的核心處理流程。JVM在GC時(shí)如果當(dāng)前對(duì)象只被Reference對(duì)象引用,JVM會(huì)根據(jù)Reference具體類型與堆內(nèi)存的使用情況決定是否把對(duì)應(yīng)的Reference對(duì)象加入到一個(gè)由Reference構(gòu)成的pending鏈表上,如果能加入pending鏈表JVM同時(shí)會(huì)通知ReferenceHandler線程進(jìn)行處理。ReferenceHandler線程收到通知后會(huì)調(diào)用Cleaner#clean或ReferenceQueue#enqueue方法進(jìn)行處理。如果引用當(dāng)前對(duì)象的Reference類型為WeakReference且堆內(nèi)存不足,那么JMV就會(huì)把WeakReference加入到pending-Reference鏈表上,然后ReferenceHandler線程收到通知后會(huì)異步地做入隊(duì)列操作。而我們的應(yīng)用程序中的線程便可以不斷地去拉取ReferenceQueue中的元素來感知JMV的堆內(nèi)存是否出現(xiàn)了不足的情況,最終達(dá)到根據(jù)堆內(nèi)存的情況來做一些處理的操作。實(shí)際上WeakHashMap低層便是過通上述過程實(shí)現(xiàn)的,只不過實(shí)現(xiàn)細(xì)節(jié)上有所偏差,這個(gè)后面再分析。再來看看ReferenceHandler線程收到通知后可能會(huì)調(diào)用的另外一個(gè)類Cleaner的實(shí)現(xiàn)。
同樣先看一下Cleaner的成員變量,再看主要的方法實(shí)現(xiàn)。

//繼承了PhantomReference類也就是虛引用,PhantomReference源碼很簡(jiǎn)單只是重寫了get方法返回null
public class Cleaner extends PhantomReference<Object> {
	/* 虛隊(duì)列,命名很到位。之前說CG把ReferenceQueue加入pending-Reference鏈中后,ReferenceHandler線程在處理時(shí)
     * 是不會(huì)將對(duì)應(yīng)的Reference加入列隊(duì)的,而是調(diào)用Cleaner.clean方法。但如果Reference不注冊(cè)ReferenceQueue,GC處理時(shí)
     * 又無法把他加入到pending-Reference鏈中,所以Cleaner里面有了一個(gè)dummyQueue成員變量。
     */
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    //Cleaner鏈表的頭結(jié)點(diǎn)
    private static Cleaner first = null;
    //當(dāng)前Cleaner節(jié)點(diǎn)的后續(xù)節(jié)點(diǎn)
    private Cleaner next = null;
    //當(dāng)前Cleaner節(jié)點(diǎn)的前續(xù)節(jié)點(diǎn)
    private Cleaner prev = null;
    //真正執(zhí)行清理工作的Runnable對(duì)象,實(shí)際clean內(nèi)部調(diào)用thunk.run()方法
    private final Runnable thunk;
    //省略部分代碼...
}

從上面的成變量分析知道Cleaner實(shí)現(xiàn)了雙向鏈表的結(jié)構(gòu)。先看構(gòu)造函數(shù)與clean方法。

//私有方法,不能直接new
private Cleaner(Object var1, Runnable var2) {
    super(var1, dummyQueue);
    this.thunk = var2;
}
//創(chuàng)建Cleaner對(duì)象,同時(shí)加入Cleaner鏈中。
public static Cleaner create(Object var0, Runnable var1) {
    return var1 == null ? null : add(new Cleaner(var0, var1));
}
//頭插法將新創(chuàng)意的Cleaner對(duì)象加入雙向鏈表,synchronized保證同步
private static synchronized Cleaner add(Cleaner var0) {
    if (first != null) {
        var0.next = first;
        first.prev = var0;
    }
    //更新頭節(jié)點(diǎn)引用
    first = var0;
    return var0;
}

public void clean() {
	//從Cleaner鏈表中先移除當(dāng)前節(jié)點(diǎn)
    if (remove(this)) {
        try {
        	//調(diào)用thunk.run()方法執(zhí)行對(duì)應(yīng)清理邏輯
            this.thunk.run();
        } catch (final Throwable var2) {
           //省略部分代碼..
        }

    }
}

可以看到Cleaner的實(shí)現(xiàn)還是比較簡(jiǎn)單,Cleaner實(shí)現(xiàn)為PhantomReference類型的引用。當(dāng)JVM GC時(shí)如果發(fā)現(xiàn)當(dāng)前處理的對(duì)象只被PhantomReference類型對(duì)象引用,同之前說的一樣其會(huì)將該Reference加pending-Reference鏈中上,只是ReferenceHandler線程在處理時(shí)如果PhantomReference類型實(shí)際類型又是Cleaner的話。其就是調(diào)用Cleaner.clean方法做清理邏輯處理。Cleaner實(shí)際是DirectByteBuffer分配的堆外內(nèi)存收回的實(shí)現(xiàn),具體見下面的分析。

DirectByteBuffer堆外內(nèi)存回收與WeakHashMap敏感內(nèi)存回收

繞開了一大圈終于回到了文章最開始提到的兩個(gè)問題,先來看一下分配給DirectByteBuffer堆外內(nèi)存是如何回收的。在創(chuàng)建DirectByteBuffer時(shí)我們實(shí)際是調(diào)用ByteBuffer#allocateDirect方法,而其實(shí)現(xiàn)如下:

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer(int cap) {
    //省略部分代碼...
    try {
    	//調(diào)用unsafe分配內(nèi)存
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
       //省略部分代碼...
    }
    //省略部分代碼...
    //前面分析中的Cleaner對(duì)象創(chuàng)建,持有當(dāng)前DirectByteBuffer的引用
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

里面和DirectByteBuffer堆外內(nèi)存回收相關(guān)的代碼便是Cleaner.create(this, new Deallocator(base, size, cap))這部分。還記得之前說實(shí)際的清理邏輯是里面和DirectByteBuffer堆外內(nèi)存回收相關(guān)的代碼便是Cleaner里面的Runnable#run方法嗎?直接看Deallocator.run方法源碼:

public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    //通過unsafe.freeMemory釋放創(chuàng)建的堆外內(nèi)存
    unsafe.freeMemory(address);
    address = 0;
    Bits.unreserveMemory(size, capacity);
}

終于找到了分配給DirectByteBuffer堆外內(nèi)存是如何回收的的答案。再總結(jié)一下,創(chuàng)建DirectByteBuffer對(duì)象時(shí)會(huì)創(chuàng)建一個(gè)Cleaner對(duì)象,Cleaner對(duì)象持有了DirectByteBuffer對(duì)象的引用。當(dāng)JVM在GC時(shí),如果發(fā)現(xiàn)DirectByteBuffer被地方法沒被引用啦,JVM會(huì)將其對(duì)應(yīng)的Cleaner加入到pending-reference鏈表中,同時(shí)通知ReferenceHandler線程處理,ReferenceHandler收到通知后,會(huì)調(diào)用Cleaner#clean方法,而對(duì)于DirectByteBuffer創(chuàng)建的Cleaner對(duì)象其clean方法內(nèi)部會(huì)調(diào)用unsafe.freeMemory釋放堆外內(nèi)存。最終達(dá)到了DirectByteBuffer對(duì)象被GC回收其對(duì)應(yīng)的堆外內(nèi)存也被回收的目的。
再來看一下文章開始提到的另外一個(gè)問題WeakHashMap如何實(shí)現(xiàn)敏感內(nèi)存的回收。實(shí)際WeakHashMap實(shí)現(xiàn)上其Entry繼承了WeakReference。

//Entry繼承了WeakReference, WeakReference引用的是Map的key
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    final int hash;
    Entry<K,V> next;
    /**
     * 創(chuàng)建Entry對(duì)象,上面分析過的ReferenceQueue,這個(gè)queue實(shí)際是WeakHashMap的成員變量,
     * 創(chuàng)建WeakHashMap時(shí)其便被初始化 final ReferenceQueue<Object> queue = new ReferenceQueue<>()
     */
    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;
    }
    //省略部分原碼...
}

往WeakHashMap添加元素時(shí),實(shí)際都會(huì)調(diào)用Entry的構(gòu)造方法,也就是會(huì)創(chuàng)建一個(gè)WeakReference對(duì)象,這個(gè)對(duì)象的引用的是WeakHashMap剛加入的Key,而所有的WeakReference對(duì)象關(guān)聯(lián)在同一個(gè)ReferenceQueue上。我們上面說過JVM在GC時(shí),如果發(fā)現(xiàn)當(dāng)前對(duì)象只有被WeakReference對(duì)象引用,那么會(huì)把其對(duì)應(yīng)的WeakReference對(duì)象加入到pending-reference鏈表上,并通知ReferenceHandler線程處理。而ReferenceHandler線程收到通知后,對(duì)于WeakReference對(duì)象會(huì)調(diào)用ReferenceQueue#enqueue方法把他加入隊(duì)列里面?,F(xiàn)在我們只要關(guān)注queue里面的元素在WeakHashMap里面是在哪里被拿出去啦做了什么樣的操作,就能找到文章開始問題的答案啦。最終能定位到WeakHashMap的expungeStaleEntries方法。

private void expungeStaleEntries() {
    //不斷地從ReferenceQueue中取出,那些只有被WeakReference對(duì)象引用的對(duì)象的Reference
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            //轉(zhuǎn)為 entry
            Entry<K,V> e = (Entry<K,V>) x;
            //計(jì)算其對(duì)應(yīng)的桶的下標(biāo)
            int i = indexFor(e.hash, table.length);
            //取出桶中元素
            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            //桶中對(duì)應(yīng)位置有元素,遍歷桶鏈表所有元素
            while (p != null) {
                Entry<K,V> next = p.next;
                //如果當(dāng)前元素(也就是entry)與queue取出的一致,將entry從鏈表中去除
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // Must not null out e.next;
                    //清空entry對(duì)應(yīng)的value
                    e.value = null;
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

現(xiàn)在只看一下WeakHashMap哪些地方會(huì)調(diào)用expungeStaleEntries方法就知道什么時(shí)候WeakHashMap里面的Key變得軟可達(dá)時(shí)我們就可以將其對(duì)應(yīng)的Entry從WeakHashMap里面移除。直接調(diào)用有三個(gè)地方分別是getTable方法、size方法、resize方法。 getTable方法又被很多地方調(diào)用如get、containsKey、put、remove、containsValue、replaceAll。最終看下來,只要對(duì)WeakHashMap進(jìn)行操作就行調(diào)用expungeStaleEntries方法。所有只要操作了WeakHashMap,沒WeakHashMap里面被再用到的Key對(duì)應(yīng)的Entry就會(huì)被清除。再來總結(jié)一下,為什么WeakHashMap適合作為內(nèi)存敏感緩存的實(shí)現(xiàn)。當(dāng)JVM 在GC時(shí),如果發(fā)現(xiàn)WeakHashMap里面某些Key沒地方在被引用啦(WeakReference除外),JVM會(huì)將其對(duì)應(yīng)的WeakReference對(duì)象加入到pending-reference鏈表上,并通知ReferenceHandler線程處理。而ReferenceHandler線程收到通知后將對(duì)應(yīng)引用Key的WeakReference對(duì)象加入到 WeakHashMap內(nèi)部的ReferenceQueue中,下次再對(duì)WeakHashMap做操作時(shí),WeakHashMap內(nèi)部會(huì)清除那些沒有被引用的Key對(duì)應(yīng)的Entry。這樣就達(dá)到了每操作WeakHashMap時(shí),自動(dòng)的檢索并清量沒有被引用的Key對(duì)應(yīng)的Entry的目地。

總結(jié)

本文通過兩個(gè)問題引出了JDK中Reference相關(guān)類的源碼分析,最終給出了問題的答案。但實(shí)際上一般開發(fā)規(guī)范中都會(huì)建議禁止重寫Object#finalize方法同樣與Reference類關(guān)系密切(具體而言是Finalizer類)。受篇幅的限制本文并未給出分析,有待各位自己看源碼啦。半年沒有寫文章啦,有點(diǎn)對(duì)不住關(guān)注的小伙伴。希望看完本文各位或多或少能有所收獲。如果覺得本文不錯(cuò)就幫忙轉(zhuǎn)發(fā)記得標(biāo)一下出處,謝謝。后面我還會(huì)繼續(xù)分享一些自己覺得比較重要的東西給大家。由于個(gè)人能力有限,文中不足與錯(cuò)誤還望指正。

以上就是java編程Reference核心原理示例源碼分析的詳細(xì)內(nèi)容,更多關(guān)于Reference核心原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java調(diào)用WebService服務(wù)的四種方法總結(jié)

    java調(diào)用WebService服務(wù)的四種方法總結(jié)

    WebService是一種跨編程語言、跨操作系統(tǒng)平臺(tái)的遠(yuǎn)程調(diào)用技術(shù),已存在很多年了,很多接口也都是通過WebService方式來發(fā)布的,下面這篇文章主要給大家介紹了關(guān)于java調(diào)用WebService服務(wù)的四種方法,需要的朋友可以參考下
    2021-11-11
  • SpringMVC異常處理器編寫及配置

    SpringMVC異常處理器編寫及配置

    這篇文章主要介紹了SpringMVC異常處理器編寫及配置,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • 詳解用JWT對(duì)SpringCloud進(jìn)行認(rèn)證和鑒權(quán)

    詳解用JWT對(duì)SpringCloud進(jìn)行認(rèn)證和鑒權(quán)

    這篇文章主要介紹了詳解用JWT對(duì)SpringCloud進(jìn)行認(rèn)證和鑒權(quán),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Springboot動(dòng)態(tài)配置AOP切點(diǎn)詳解

    Springboot動(dòng)態(tài)配置AOP切點(diǎn)詳解

    這篇文章主要介紹了Springboot動(dòng)態(tài)配置AOP切點(diǎn)詳解,Springboot 可以定義注解切點(diǎn)去攔截注解修飾的類方法以及execution(xxxx)切點(diǎn)去攔截具體的類方法,默認(rèn)情況下我們都會(huì)使用注解@PointCut去定義切點(diǎn),然后定義切面攔截切點(diǎn),需要的朋友可以參考下
    2023-09-09
  • Java獲取文件夾下所有文件名稱的方法示例

    Java獲取文件夾下所有文件名稱的方法示例

    這篇文章主要介紹了Java獲取文件夾下所有文件名稱的方法,涉及java針對(duì)文件與目錄相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06
  • 談?wù)凧ava中Volatile關(guān)鍵字的理解

    談?wù)凧ava中Volatile關(guān)鍵字的理解

    volatile這個(gè)關(guān)鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個(gè)備受爭(zhēng)議的關(guān)鍵字,因?yàn)樵诔绦蛑惺褂盟鶗?huì)導(dǎo)致出人意料的結(jié)果,本文給大家介紹java中volatile關(guān)鍵字,需要的朋友參考下
    2016-03-03
  • spring cloud中啟動(dòng)Eureka Server的方法

    spring cloud中啟動(dòng)Eureka Server的方法

    本篇文章主要介紹了spring cloud中啟動(dòng)Eureka Server的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • Spring常用注解匯總

    Spring常用注解匯總

    這篇文章主要介紹了Spring常用注解匯總,需要的朋友可以參考下
    2014-08-08
  • spring cloud 使用Zuul 實(shí)現(xiàn)API網(wǎng)關(guān)服務(wù)問題

    spring cloud 使用Zuul 實(shí)現(xiàn)API網(wǎng)關(guān)服務(wù)問題

    這篇文章主要介紹了spring cloud 使用Zuul 實(shí)現(xiàn)API網(wǎng)關(guān)服務(wù)問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-05-05
  • 詳述 DB2 分頁(yè)查詢及 Java 實(shí)現(xiàn)的示例

    詳述 DB2 分頁(yè)查詢及 Java 實(shí)現(xiàn)的示例

    本篇文章主要介紹了詳述 DB2 分頁(yè)查詢及 Java 實(shí)現(xiàn)的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09

最新評(píng)論