Java中一共有4種引用類型(其實還有一些其餘的引用類型好比FinalReference):強引用、軟引用、弱引用、虛引用。其中強引用就是咱們常用的Object a = new Object();
這樣的形式,在Java中並無對應的Reference類。java
本篇文章主要是分析軟引用、弱引用、虛引用的實現,這三種引用類型都是繼承於Reference這個類,主要邏輯也在Reference中。併發
在分析前,先拋幾個問題?jvm
1.網上大多數文章對於軟引用的介紹是:在內存不足的時候纔會被回收,那內存不足是怎麼定義的?什麼才叫內存不足?分佈式
2.網上大多數文章對於虛引用的介紹是:形同虛設,虛引用並不會決定對象的生命週期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?ide
3.虛引用在Jdk中有哪些場景下用到了呢?函數
咱們先看下Reference.java
中的幾個字段高併發
public abstract class Reference<T> { //引用的對象 private T referent; //回收隊列,由使用者在Reference的構造函數中指定 volatile ReferenceQueue<? super T> queue; //當該引用被加入到queue中的時候,該字段被設置爲queue中的下一個元素,以造成鏈表結構 volatile Reference next; //在GC時,JVM底層會維護一個叫DiscoveredList的鏈表,存放的是Reference對象,discovered字段指向的就是鏈表中的下一個元素,由JVM設置 transient private Reference<T> discovered; //進行線程同步的鎖對象 static private class Lock { } private static Lock lock = new Lock(); //等待加入queue的Reference對象,在GC時由JVM設置,會有一個java層的線程(ReferenceHandler)源源不斷的從pending中提取元素加入到queue private static Reference<Object> pending = null; }
一個Reference對象的生命週期以下:oop
主要分爲Native層和Java層兩個部分。ui
Native層在GC時將須要被回收的Reference對象加入到DiscoveredList中(代碼在referenceProcessor.cpp
中process_discovered_references
方法),而後將DiscoveredList的元素移動到PendingList中(代碼在referenceProcessor.cpp
中enqueue_discovered_ref_helper
方法),PendingList的隊首就是Reference類中的pending對象。this
看看Java層的代碼
private static class ReferenceHandler extends Thread { ... public void run() { while (true) { tryHandlePending(true); } } } static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; //若是是Cleaner對象,則記錄下來,下面作特殊處理 c = r instanceof Cleaner ? (Cleaner) r : null; //指向PendingList的下一個對象 pending = r.discovered; r.discovered = null; } else { //若是pending爲null就先等待,當有對象加入到PendingList中時,jvm會執行notify if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } } ... // 若是時CLeaner對象,則調用clean方法進行資源回收 if (c != null) { c.clean(); return true; } //將Reference加入到ReferenceQueue,開發者能夠經過從ReferenceQueue中poll元素感知到對象被回收的事件。 ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }
流程比較簡單:就是源源不斷的從PendingList中提取出元素,而後將其加入到ReferenceQueue中去,開發者能夠經過從ReferenceQueue中poll元素感知到對象被回收的事件。
另外須要注意的是,對於Cleaner類型(繼承自虛引用)的對象會有額外的處理:在其指向的對象被回收時,會調用clean方法,該方法主要是用來作對應的資源回收,在堆外內存DirectByteBuffer中就是用Cleaner進行堆外內存的回收,這也是虛引用在java中的典型應用。
看完了Reference的實現,再看看幾個實現類裏,各自有什麼不一樣。
public class SoftReference<T> extends Reference<T> { static private long clock; 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; } }
軟引用的實現很簡單,就多了兩個字段:clock
和timestamp
。clock
是個靜態變量,每次GC時都會將該字段設置成當前時間。timestamp
字段則會在每次調用get方法時將其賦值爲clock
(若是不相等且對象沒被回收)。
那這兩個字段的做用是什麼呢?這和軟引用在內存不夠的時候才被回收,又有什麼關係呢?
這些還得看JVM的源碼才行,由於決定對象是否須要被回收都是在GC中實現的。
size_t ReferenceProcessor::process_discovered_reflist( DiscoveredList refs_lists[], ReferencePolicy* policy, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor) { ... //還記得上文提到過的DiscoveredList嗎?refs_lists就是DiscoveredList。 //對於DiscoveredList的處理分爲幾個階段,SoftReference的處理就在第一階段 ... for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy, is_alive, keep_alive, complete_gc); } ... } //該階段的主要目的就是當內存足夠時,將對應的SoftReference從refs_list中移除。 void ReferenceProcessor::process_phase1(DiscoveredList& refs_list, ReferencePolicy* policy, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc) { DiscoveredListIterator iter(refs_list, keep_alive, is_alive); // Decide which softly reachable refs should be kept alive. while (iter.has_next()) { iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */)); //判斷引用的對象是否存活 bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive(); //若是引用的對象已經不存活了,則會去調用對應的ReferencePolicy判斷該對象是不時要被回收 if (referent_is_dead && !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) { if (TraceReferenceGC) { gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s" ") by policy", (void *)iter.obj(), iter.obj()->klass()->internal_name()); } // Remove Reference object from list iter.remove(); // Make the Reference object active again iter.make_active(); // keep the referent around iter.make_referent_alive(); iter.move_to_next(); } else { iter.next(); } } ... }
refs_lists
中存放了本次GC發現的某種引用類型(虛引用、軟引用、弱引用等),而process_discovered_reflist
方法的做用就是將不須要被回收的對象從refs_lists
移除掉,refs_lists
最後剩下的元素全是須要被回收的元素,最後會將其第一個元素賦值給上文提到過的Reference.java#pending
字段。
ReferencePolicy一共有4種實現:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永遠返回false,表明永遠不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠返回true,在referenceProcessor.hpp#setup
方法中中能夠設置policy爲AlwaysClearPolicy,至於何時會用到AlwaysClearPolicy,你們有興趣能夠自行研究。
LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法則是徹底相同:
bool LRUMaxHeapPolicy::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; }
timestamp_clock
就是SoftReference的靜態字段clock
,java_lang_ref_SoftReference::timestamp(p)
對應是字段timestamp
。若是上次GC後有調用SoftReference#get
,interval
值爲0,不然爲若干次GC之間的時間差。
_max_interval
則表明了一個臨界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy兩種策略中有差別。
void LRUCurrentHeapPolicy::setup() { _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB; assert(_max_interval >= 0,"Sanity check"); } void LRUMaxHeapPolicy::setup() { size_t max_heap = MaxHeapSize; max_heap -= Universe::get_heap_used_at_last_gc(); max_heap /= M; _max_interval = max_heap * SoftRefLRUPolicyMSPerMB; assert(_max_interval >= 0,"Sanity check"); }
其中SoftRefLRUPolicyMSPerMB
默認爲1000,前者的計算方法和上次GC後可用堆大小有關,後者計算方法和(堆大小-上次gc時堆使用大小)有關。
看到這裏你就知道SoftReference到底何時被被回收了,它和使用的策略(默認應該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次調用get方法的時間都有關係。
public class WeakReference<T> extends Reference<T> { public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
能夠看到WeakReference在Java層只是繼承了Reference,沒有作任何的改動。那referent字段是何時被置爲null的呢?要搞清楚這個問題咱們再看下上文提到過的process_discovered_reflist
方法:
size_t ReferenceProcessor::process_discovered_reflist( DiscoveredList refs_lists[], ReferencePolicy* policy, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor) { ... //Phase 1:將全部不存活可是還不能被回收的軟引用從refs_lists中移除(只有refs_lists爲軟引用的時候,這裏policy纔不爲null) if (policy != NULL) { if (mt_processing) { RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/); task_executor->execute(phase1); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy, is_alive, keep_alive, complete_gc); } } } else { // policy == NULL assert(refs_lists != _discoveredSoftRefs, "Policy must be specified for soft references."); } // Phase 2: // 移除全部指向對象還存活的引用 if (mt_processing) { RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/); task_executor->execute(phase2); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc); } } // Phase 3: // 根據clear_referent的值決定是否將不存活對象回收 if (mt_processing) { RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/); task_executor->execute(phase3); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase3(refs_lists[i], clear_referent, is_alive, keep_alive, complete_gc); } } return total_list_count; } void ReferenceProcessor::process_phase3(DiscoveredList& refs_list, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc) { ResourceMark rm; DiscoveredListIterator iter(refs_list, keep_alive, is_alive); while (iter.has_next()) { iter.update_discovered(); iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */)); if (clear_referent) { // NULL out referent pointer //將Reference的referent字段置爲null,以後會被GC回收 iter.clear_referent(); } else { // keep the referent around //標記引用的對象爲存活,該對象在此次GC將不會被回收 iter.make_referent_alive(); } ... } ... }
不論是弱引用仍是其餘引用類型,將字段referent置null的操做都發生在process_phase3
中,而具體行爲是由clear_referent
的值決定的。而clear_referent
的值則和引用類型相關。
ReferenceProcessorStats ReferenceProcessor::process_discovered_references( BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor, GCTimer* gc_timer) { NOT_PRODUCT(verify_ok_to_handle_reflists()); ... //process_discovered_reflist方法的第3個字段就是clear_referent // Soft references size_t soft_count = 0; { GCTraceTime tt("SoftReference", trace_time, false, gc_timer); soft_count = process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } update_soft_ref_master_clock(); // Weak references size_t weak_count = 0; { GCTraceTime tt("WeakReference", trace_time, false, gc_timer); weak_count = process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); } // Final references size_t final_count = 0; { GCTraceTime tt("FinalReference", trace_time, false, gc_timer); final_count = process_discovered_reflist(_discoveredFinalRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); } // Phantom references size_t phantom_count = 0; { GCTraceTime tt("PhantomReference", trace_time, false, gc_timer); phantom_count = process_discovered_reflist(_discoveredPhantomRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); } ... }
能夠看到,對於Soft references和Weak references clear_referent
字段傳入的都是true,這也符合咱們的預期:對象不可達後,引用字段就會被置爲null,而後對象就會被回收(對於軟引用來講,若是內存足夠的話,在Phase 1,相關的引用就會從refs_list中被移除,到Phase 3時refs_list爲空集合)。
但對於Final references和 Phantom references,clear_referent
字段傳入的是false,也就意味着被這兩種引用類型引用的對象,若是沒有其餘額外處理,只要Reference對象還存活,那引用的對象是不會被回收的。Final references和對象是否重寫了finalize方法有關,不在本文分析範圍以內,咱們接下來看看Phantom references。
public class PhantomReference<T> extends Reference<T> { public T get() { return null; } public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
能夠看到虛引用的get方法永遠返回null,咱們看個demo。
public static void demo() throws InterruptedException { Object obj = new Object(); ReferenceQueue<Object> refQueue =new ReferenceQueue<>(); PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue); Object objg = phanRef.get(); //這裏拿到的是null System.out.println(objg); //讓obj變成垃圾 obj=null; System.gc(); Thread.sleep(3000); //gc後會將phanRef加入到refQueue中 Reference<? extends Object> phanRefP = refQueue.remove(); //這裏輸出true System.out.println(phanRefP==phanRef); }
從以上代碼中能夠看到,虛引用可以在指向對象不可達時獲得一個'通知'(其實全部繼承References的類都有這個功能),須要注意的是GC完成後,phanRef.referent依然指向以前建立Object,也就是說Object對象一直沒被回收!
而形成這一現象的緣由在上一小節末尾已經說了:對於Final references和 Phantom references,
clear_referent字段傳入的時false,也就意味着被這兩種引用類型引用的對象,若是沒有其餘額外處理,在GC中是不會被回收的。
對於虛引用來講,從refQueue.remove();
獲得引用對象後,能夠調用clear
方法強行解除引用和對象之間的關係,使得對象下次能夠GC時能夠被回收掉。
針對文章開頭提出的幾個問題,看完分析,咱們已經能給出回答:
1.咱們常常在網上看到軟引用的介紹是:在內存不足的時候纔會回收,那內存不足是怎麼定義的?爲何才叫內存不足?
軟引用會在內存不足時被回收,內存不足的定義和該引用對象get的時間以及當前堆可用內存大小都有關係,計算公式在上文中也已經給出。
2.網上對於虛引用的介紹是:形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?
嚴格的說,虛引用是會影響對象生命週期的,若是不作任何處理,只要虛引用不被回收,那其引用的對象永遠不會被回收。因此通常來講,從ReferenceQueue中得到PhantomReference對象後,若是PhantomReference對象不會被回收的話(好比被其餘GC ROOT可達的對象引用),須要調用clear
方法解除PhantomReference和其引用對象的引用關係。
3.虛引用在Jdk中有哪些場景下用到了呢?
DirectByteBuffer中是用虛引用的子類Cleaner.java
來實現堆外內存回收的,後續會寫篇文章來講說堆外內存的裏裏外外。
本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q