JDK源碼分析(8)之Reference完全解讀

在閱讀本文之前最好對(duì) Reference 框架有一個(gè)整體的把握,可以參考我上一篇文章?Reference 框架概覽?;本文主要講了?Reference?的子類(lèi)實(shí)現(xiàn)和應(yīng)用(SoftReference,WeakReference,PhantomReference);

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的寧陽(yáng)網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

Java 引用的強(qiáng)弱關(guān)系StrongReference > SoftReference > WeakReference > PhantomReference

一、StrongReference

強(qiáng)引用:我們通常使用的引用,形如Object o = new Object();

JDK源碼分析(8)之 Reference 完全解讀

此時(shí)從 stack 中的 o,到 heap 中的 Object 就是強(qiáng)引用;其他引用強(qiáng)弱的判定規(guī)則,可以查看我上一篇文章Reference 框架概覽?;

二、SoftReference

軟引用:可以用來(lái)表示一些有用但非必須的對(duì)象;JVM 會(huì)根據(jù)使用率和剩余堆空間大小來(lái)公共決定什么時(shí)候回收 SoftReference;JVM 保證在拋出 OOM 之前會(huì)再次掃描回收這些軟引用,如果回收后內(nèi)存仍不足才會(huì)拋出 OOM;所以在源碼的注釋中也寫(xiě)了 SoftReference 適合實(shí)現(xiàn)內(nèi)存敏感的緩存;

public?class?SoftReference<T>?extends?Reference<T>?{??/**
???*?Timestamp?clock,?updated?by?the?garbage?collector
???*/
??static?private?long?clock;??/**
???*?Timestamp?updated?by?each?invocation?of?the?get?method.??The?VM?may?use
???*?this?field?when?selecting?soft?references?to?be?cleared,?but?it?is?not
???*?required?to?do?so.
???*/
??private?long?timestamp;??public?SoftReference(T?referent)?{????super(referent);????this.timestamp?=?clock;
??}??public?SoftReference(T?referent,?ReferenceQueue<??super?T>?q)?{????super(referent,?q);????this.timestamp?=?clock;
??}??public?T?get()?{
????T?o?=?super.get();????if?(o?!=?null?&&?this.timestamp?!=?clock)??????this.timestamp?=?clock;????return?o;
??}

}

看上面的代碼,SoftReference 與 Reference 相比多了兩個(gè)時(shí)間戳?clock,timestamp,并且會(huì)在每次?get的時(shí)候更新時(shí)間戳;

  • clock:這個(gè)時(shí)間戳是static修飾的,是所有 SoftReference 共有,由 JVM 維護(hù);

  • timestamp:主要用于記錄當(dāng)前對(duì)象的存活時(shí)間;

回收策略

上面提到 SoftReference 的回收是由使用率和剩余堆空間大小來(lái)公共決定的,那么它是怎么實(shí)現(xiàn)的呢?

openjdk/hotspot/src/share/vm/memory/referencePolicy.cpp

//?Capture?state?(of-the-VM)?information?needed?to?evaluate?the?policyvoid?LRUCurrentHeapPolicy::setup()?{
??_max_interval?=?(Universe::get_heap_free_at_last_gc()?/?M)?*?SoftRefLRUPolicyMSPerMB;
??assert(_max_interval?>=?0,"Sanity?check");
}//?The?oop?passed?in?is?the?SoftReference?object,?and?not//?the?object?the?SoftReference?points?to.bool?LRUCurrentHeapPolicy::should_clear_reference(oop?p,?jlong?timestamp_clock)?{
??jlong?interval?=?timestamp_clock?-?java_lang_ref_SoftReference::timestamp(p);
??assert(interval?>=?0,?"Sanity?check");??//?The?interval?will?be?zero?if?the?ref?was?accessed?since?the?last?scavenge/gc.
??if(interval?<=?_max_interval)?{????return?false;
??}??return?true;
}

根據(jù)上面的代碼可以大致知道:

  1. 首先計(jì)算出了最大堆內(nèi)存和上次 GC 時(shí)剩余的內(nèi)存;

  2. 再用(剩余內(nèi)存 / 最大內(nèi)存 )* SoftRefLRUPolicyMSPerMB 得出到下次 GC 期間軟引用的最大 idle 時(shí)間;

  3. 最后用 clock 和 timestamp 兩個(gè)時(shí)間戳差值得到 SoftReference 的 idle 時(shí)間(每次 get 的時(shí)候?this.timestamp = clock;,所以get 之后 idle 時(shí)間歸零),如果大于最大 idle 時(shí)間則清除;

我們可以簡(jiǎn)單測(cè)試一下,啟動(dòng)參數(shù):-XX:SoftRefLRUPolicyMSPerMB=2 -Xmx10M -XX:+PrintCommandLineFlags -verbose:gc;

  • -XX:SoftRefLRUPolicyMSPerMB=2:可以參照上面的計(jì)算過(guò)程調(diào)節(jié) SoftReference 的回收頻率;

  • -Xmx10M:為最大堆內(nèi)存,同樣可以自行調(diào)節(jié),-verbose:gc:打開(kāi) GC 日志,-XX:+PrintCommandLineFlags:打印 JVM 啟動(dòng)參數(shù);

private?static?void?test03()?throws?InterruptedException?{
??ReferenceQueue?queue?=?new?ReferenceQueue();
??Object?o?=?new?Object()?{????@Override
????public?String?toString()?{??????return?"zhangsan";
????}
??};
??
??Reference?softRef?=?new?SoftReference(o,?queue);??new?Monitor(queue).start();
??
??o?=?null;
??System.gc();
??log.info("o=null,?referent:{}",?softRef.get());??
??byte[]?bytes?=?new?byte[3?*?1024?*?1024];
??System.gc();
??log.info("After?GC,?referent:{}",?softRef.get());
??Thread.sleep(2000);
??System.gc();
??log.info("After?GC,?referent:{}",?softRef.get());
}private?static?class?Monitor?extends?Thread?{
??ReferenceQueue?queue;??public?Monitor(ReferenceQueue?queue)?{????this.queue?=?queue;
??}??
??@Override
??public?void?run()?{????while?(true)?{??????try?{
????????log.info("remove?reference:{}",?queue.remove().toString());
??????}?catch?(InterruptedException?e)?{
????????e.printStackTrace();
??????}
????}
??}
}

// 打?。?/p>

[main]?o=null,?referent:zhangsan
[main]?After?GC,?referent:zhangsan
[main]?After?GC,?referent:null[Thread-0]?remove?reference:java.lang.ref.SoftReference@bcffe9a

根據(jù)不同的參數(shù)設(shè)置會(huì)出現(xiàn)不同的情況,大家可以自行調(diào)節(jié)參數(shù),驗(yàn)證上面的計(jì)算規(guī)則;另外如果-XX:SoftRefLRUPolicyMSPerMB=0,那么 SoftReference 就應(yīng)該和 WeakReference 差不多了,至于是否完全一致,就留到以后查看 JVM 的時(shí)候再確定了;

三、WeakReference

弱引用:被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次 GC,當(dāng) GC 的時(shí)候無(wú)論內(nèi)存是否足夠,使用是否頻繁都會(huì)被清除;同樣源碼注釋里面也寫(xiě)了 WeakReference 適合實(shí)現(xiàn) canonicalizing mappings,比如 WeakHashMap;

public?class?WeakReference<T>?extends?Reference<T>?{??public?WeakReference(T?referent)?{????super(referent);
??}??public?WeakReference(T?referent,?ReferenceQueue<??super?T>?q)?{????super(referent,?q);
??}
}

簡(jiǎn)單測(cè)試,啟動(dòng)參數(shù):-Xmx300M -XX:+PrintCommandLineFlags -verbose:gc;

private?static?void?test04()?{
??ReferenceQueue?queue?=?new?ReferenceQueue();
??Object?o?=?new?Object()?{????@Override
????public?String?toString()?{??????return?"zhangsan";
????}
??};
??
??Reference?ref?=?new?WeakReference(o,?queue);??new?Monitor(queue).start();
??
??o?=?null;
??log.info("Before?GC,?referent:{}",?ref.get());
??System.gc();
??log.info("After?GC,?referent:{}",?ref.get());
}

// 打?。?/p>

[main]?????Before?GC,?referent:zhangsan[main]?????After?GC,?referent:null[Thread-0]?remove?reference:java.lang.ref.WeakReference@67ac4ff0

可以看到在內(nèi)存足夠的時(shí)候,referent 被清除,WeakReference 在下次 GC 的時(shí)候隨機(jī)被清除,并且 ReferenceQueue 也收到了事件通知;

四、PhantomReference

虛引用:最弱的一種引用關(guān)系,虛引用對(duì)一個(gè)對(duì)象的生命周期完全沒(méi)有影響,設(shè)置虛引用的唯一目的就是得到 referent 被回收的事件通知;

public?class?PhantomReference<T>?extends?Reference<T>?{????public?T?get()?{????????return?null;
????}????public?PhantomReference(T?referent,?ReferenceQueue<??super?T>?q)?{????????super(referent,?q);
????}
}

從源碼也能看到 get 的時(shí)候,永遠(yuǎn)返回 null;

同樣簡(jiǎn)單測(cè)試一下,

private?static?void?test06()?{
??ReferenceQueue?queue?=?new?ReferenceQueue();
??Object?o?=?new?Object()?{????@Override
????public?String?toString()?{??????return?"zhangsan";
????}
??};
??
??Reference?ref?=?new?PhantomReference(o,?queue);??new?Monitor(queue).start();
??
??o?=?null;
??log.info("Before?GC,?referent:{}",?ref.get());
??System.gc();
??log.info("After?GC,?referent:{}",?ref.get());
}

// 打印:

[main]?????Before?GC,?referent:null[main]?????After?GC,?referent:null[Thread-0]?remove?reference:java.lang.ref.PhantomReference@661a5fff

可以看到?PhantomReference.get()?始終為 null,并且當(dāng) referent 被回收的時(shí)候,并且 ReferenceQueue 也收到了事件通知;

此外 PhantomReference 和其他引用還有一個(gè)很大的不同,在 ReferenceQueue 中 JVM 并不會(huì)幫我們把 referent 字段置為空;

private?static?void?test07()?{
??ReferenceQueue?queue?=?new?ReferenceQueue();
??Object?o?=?new?Object()?{????@Override
????public?String?toString()?{??????return?"zhangsan";
????}
??};
??
??Reference?ref?=?new?PhantomReference(o,?queue);??new?Monitor2(queue).start();
??
??o?=?null;
??log.info("Before?GC,?referent:{}",?ref.get());
??System.gc();
??log.info("After?GC,?referent:{}",?ref.get());
}private?static?class?Monitor2?extends?Thread?{
??ReferenceQueue?queue;??
??public?Monitor2(ReferenceQueue?queue)?{????this.queue?=?queue;
??}??
??@Override
??public?void?run()?{????try?{??????while?(true)?{
????????Reference?ref?=?queue.poll();
????????log.info("remove?reference:{}",?ref);????????if?(ref?!=?null)?{
????????Field?field?=?Reference.class.getDeclaredField("referent");
????????field.setAccessible(true);
????????
????????log.info("ReferenceQueue?get?Referent:{}",?field.get(ref));
????????ref.clear();????????break;
????????}
??????}
????}?catch?(Exception?e)?{
??????e.printStackTrace();
????}
??}
}

// 打?。?/p>

[main]?????Before?GC,?referent:null[main]?????After?GC,?referent:null[Thread-0]?remove?reference:null[Thread-0]?remove?reference:java.lang.ref.PhantomReference@7b4cba2
[Thread-0]?ReferenceQueue?get?Referent:zhangsan

這里可以看到從 ReferenceQueue 中取出來(lái)的 Reference 仍然可以取到引用對(duì)象,即 referent;但是在其他引用中打印為 null,這里可以將上面例子中的 Monitor 改為 Monitor2 測(cè)試;

Cleaner
Reference.tryHandlePending()里面提到的,主要用于替代Object.finalize();

public?class?Cleaner?extends?PhantomReference<Object>?{??private?static?final?ReferenceQueue<Object>?dummyQueue?=?new?ReferenceQueue<>();??static?private?Cleaner?first?=?null;??private?Cleaner
????next?=?null,
????prev?=?null;????
??private?final?Runnable?thunk;??
??private?Cleaner(Object?referent,?Runnable?thunk)?{????super(referent,?dummyQueue);????this.thunk?=?thunk;
??}??
??public?static?Cleaner?create(Object?ob,?Runnable?thunk)?{????if?(thunk?==?null)??????return?null;????return?add(new?Cleaner(ob,?thunk));
??}??private?static?synchronized?Cleaner?add(Cleaner?cl)?{????if?(first?!=?null)?{
??????cl.next?=?first;
??????first.prev?=?cl;
????}
????first?=?cl;????return?cl;
??}??private?static?synchronized?boolean?remove(Cleaner?cl)?{?}??public?void?clean()?{????if?(!remove(this))??????return;????try?{
??????thunk.run();
????}?catch?(final?Throwable?x)?{
??????AccessController.doPrivileged(new?PrivilegedAction<Void>()?{??????????public?Void?run()?{????????????if?(System.err?!=?null)??????????????new?Error("Cleaner?terminated?abnormally",?x)
????????????????.printStackTrace();
????????????System.exit(1);????????????return?null;
??????????}});
????}
??}
}

從代碼可以看到,

  • Cleaner 只能通過(guò)工廠方法創(chuàng)建,并且所有的 Cleaner 都共同屬于同一個(gè) Reference 鏈表;

  • 代碼中的next、prev不同于 Reference 中的 next,他們組成了一個(gè)雙向鏈表;

  • Cleaner 中沒(méi)有入隊(duì)操作,在創(chuàng)建之初就已經(jīng)加入鏈表了,具體代碼可以查看Reference.tryHandlePending();

  • ReferenceQueue(dummyQueue?域)的作用不再是提供入隊(duì)和事件監(jiān)聽(tīng)功能,而僅僅是保證 GC 不會(huì)自動(dòng)將 Cleaner 給回收了;

  • Cleaner 的主要邏輯就是傳入一個(gè) clean 線(xiàn)程,在 referent 引用對(duì)象清除的時(shí)候,執(zhí)行?clean?操作;

總結(jié)

  • 對(duì)于上面講的軟引用、弱引用、虛引用,都有一套共同的事件通知機(jī)制,具體邏輯在 Reference 類(lèi)中;主要的差別在于引用回收條件的判斷,這部分代碼在 JVM 里面;

  • 另外對(duì)于 Reference 類(lèi)還有 FinalReference 沒(méi)有寫(xiě),主要用于當(dāng)類(lèi)重寫(xiě)finalize方法時(shí),JVM 會(huì)將他包裝在 FinalReference 里面,里面的細(xì)節(jié)比較多,并且一般不建議使用,所以暫時(shí)沒(méi)寫(xiě);

  • 此外《Effective Java》第三版的第八條也講了避免使用finalizer和cleaner;詳情可以自行查閱;

當(dāng)前題目:JDK源碼分析(8)之Reference完全解讀
URL網(wǎng)址:http://www.muchs.cn/article48/ghpohp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、網(wǎng)站維護(hù)網(wǎng)站內(nèi)鏈、標(biāo)簽優(yōu)化小程序開(kāi)發(fā)、

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

綿陽(yáng)服務(wù)器托管