java引用體系中我們最熟悉的就是強(qiáng)引用類型,如 A a= new A();這是我們經(jīng)常說的強(qiáng)引用StrongReference,jvm gc時(shí)會檢測對象是否存在強(qiáng)引用,如果存在由根對象對其有傳遞的強(qiáng)引用,則不會對其進(jìn)行回收,即使內(nèi)存不足拋出OutOfMemoryError。
除了強(qiáng)引用外,Java還引入了SoftReference,WeakReference,PhantomReference,F(xiàn)inalReference ,這些類放在java.lang.ref包下,類的繼承體系如下圖

Java額外引入這個(gè)四種類型引用主要目的是在jvm 在gc時(shí),按照引用類型的不同,在回收時(shí)采用不同的邏輯??梢园堰@些引用看作是對對象的一層包裹,jvm根據(jù)外層不同的包裹,對其包裹的對象采用不同的回收策略.
Reference指代引用對象本身,Referent指代被引用對象
對象可達(dá)性判斷
jvm gc時(shí),判斷一個(gè)對象是否存在引用時(shí),都是從根結(jié)合引用(Root Set of References)開始去標(biāo)識,往往到達(dá)一個(gè)對象的引用路徑會存在多條,如下圖

那么 垃圾回收時(shí)會依據(jù)兩個(gè)原則來判斷對象的可達(dá)性:
- 單一路徑中,以最弱的引用為準(zhǔn)
- 多路徑中,以最強(qiáng)的引用為準(zhǔn)
例如Obj4的引用,存在3個(gè)路徑:1->6、2->5、3->4, 那么從根對象到Obj4最強(qiáng)的引用是2->5,因?yàn)樗鼈兌际菑?qiáng)引用。如果僅僅存在一個(gè)路徑對Obj4有引用時(shí),比如現(xiàn)在只剩1->6,那么根對象到Obj4的引用就是以最弱的為準(zhǔn),就是SoftReference引用,Obj4就是softly-reachable對象。
Java最初只有普通的強(qiáng)引用,只有對象存在引用,則對象就不會被回收,即使內(nèi)存不足,也是如此,JVM會爆出OOM,也不會去回收存在引用的對象。
如果只提供強(qiáng)引用,我們就很難寫出“這個(gè)對象不是很重要,如果內(nèi)存不足GC回收掉也是可以的”這種語義的代碼。Java在1.2版本中完善了引用體系,提供了4中引用類型:強(qiáng)引用,軟引用,弱引用,虛引用。使用這些引用類型,我們不但可以控制垃圾回收器對對象的回收策略,同時(shí)還能在對象被回收后得到通知,進(jìn)行相應(yīng)的后續(xù)操作。
Java目前有4中引用類型:
- 強(qiáng)引用(Strong Reference):普通的的引用類型,new一個(gè)對象默認(rèn)得到的引用就是強(qiáng)引用,只要對象存在強(qiáng)引用,就不會被GC。
- 軟引用(Soft Reference):相對較弱的引用,垃圾回收器會在內(nèi)存不足時(shí)回收弱引用指向的對象。JVM會在拋出OOME前清理所有弱引用指向的對象,如果清理完還是內(nèi)存不足,才會拋出OOME。所以軟引用一般用于實(shí)現(xiàn)內(nèi)存敏感緩存。
- 弱引用(Weak Reference):更弱的引用類型,垃圾回收器在GC時(shí)會回收此對象,也可以用于實(shí)現(xiàn)緩存,比如JDK提供的WeakHashMap。
- 虛引用(Phantom Reference):一種特殊的引用類型,不能通過虛引用獲取到關(guān)聯(lián)對象,只是用于獲取對象被回收的通知。
SoftReference:軟引用,堆內(nèi)存不足時(shí),垃圾回收器會回收對應(yīng)引用
WeakReference:弱引用,每次垃圾回收都會回收其引用
PhantomReference:虛引用,對引用無影響,只用于獲取對象被回收的通知
FinalReference:Java用于實(shí)現(xiàn)finalization的一個(gè)內(nèi)部類
Reference的核心
Java的多種引用類型實(shí)現(xiàn),不是通過擴(kuò)展語法實(shí)現(xiàn)的,而是利用類實(shí)現(xiàn)的,Reference類表示一個(gè)引用,其核心代碼就是一個(gè)成員變量reference
public abstract class Reference<T> {
private T referent; // 會被GC特殊對待
// 獲取Reference管理的對象
public T get() {
return this.referent;
}
// ...
}
如果JVM沒有對這個(gè)變量做特殊處理,它依然只是一個(gè)普通的強(qiáng)引用,之所以會出現(xiàn)不同的引用類型,是因?yàn)镴VM垃圾回收器硬編碼識別SoftReference,WeakReference,PhantomReference等這些具體的類,對其reference變量進(jìn)行特殊對象,才有了不同的引用類型的效果。
Reference及其子類有兩大功能:
- 實(shí)現(xiàn)特定的引用類型
- 用戶可以對象被回收后得到通知
第一個(gè)功能很清楚,第二個(gè)功能是如何做到的呢?
一種思路是在新建一個(gè)Reference實(shí)例是,添加一個(gè)回調(diào),當(dāng)java.lang.ref.Reference#referent被回收時(shí),JVM調(diào)用該回調(diào),這種思路比較符合一般的通知模型,但是對于引用與垃圾回收這種底層場景來說,會導(dǎo)致實(shí)現(xiàn)復(fù)雜,性能不高的問題,比如需要考慮在什么線程中執(zhí)行這個(gè)回調(diào),回調(diào)執(zhí)行阻塞怎么辦等等。
所以Reference使用了一種更加原始的方式來做通知,就是把引用對象被回收的Reference添加到一個(gè)隊(duì)列中,用戶后續(xù)自己去從隊(duì)列中獲取并使用。
理解了設(shè)計(jì)后對應(yīng)到代碼上就好理解了,Reference有一個(gè)queue成員變量,用于存儲引用對象被回收的Reference實(shí)例:
public abstract class Reference<T> {
// 會被GC特殊對待
private T referent;
// reference被回收后,當(dāng)前Reference實(shí)例會被添加到這個(gè)隊(duì)列中
volatile ReferenceQueue<? super T> queue;
// 只傳入reference的構(gòu)造函數(shù),意味著用戶只需要特殊的引用類型,不關(guān)心對象何時(shí)被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):
- Active:新創(chuàng)建的實(shí)例的狀態(tài),由垃圾回收器進(jìn)行處理,如果實(shí)例的可達(dá)性處于合適的狀態(tài),垃圾回收器會切換實(shí)例的狀態(tài)為Pending或者Inactive。如果Reference注冊了ReferenceQueue,則會切換為Pending,并且Reference會加入pending-Reference鏈表中,如果沒有注冊ReferenceQueue,會切換為Inactive。
- Pending:在pending-Reference鏈表中的Reference的狀態(tài),這些Reference等待被加入ReferenceQueue中。
- Enqueued:在ReferenceQueue隊(duì)列中的Reference的狀態(tài),如果Reference從隊(duì)列中移除,會進(jìn)入Inactive狀態(tài)
- Inactive:Reference的最終狀態(tài)

除了上文提到的ReferenceQueue,這里出現(xiàn)了一個(gè)新的數(shù)據(jù)結(jié)構(gòu):pending-Reference。這個(gè)鏈表是用來干什么的呢?
上文提到了,reference引用的對象被回收后,該Reference實(shí)例會被添加到ReferenceQueue中,但是這個(gè)不是垃圾回收器來做的,這個(gè)操作還是有一定邏輯的。 如果垃圾回收器還需要執(zhí)行這個(gè)操作,會降低其效率。從另外一方面想,Reference實(shí)例會被添加到ReferenceQueue中的實(shí)效性要求不高,所以也沒必要在回收時(shí)立馬加入ReferenceQueue。
所以垃圾回收器做的是一個(gè)更輕量級的操作:把Reference添加到pending-Reference鏈表中。Reference對象中有一個(gè)pending成員變量,是靜態(tài)變量,它就是這個(gè)pending-Reference鏈表的頭結(jié)點(diǎn)。要組成鏈表,還需要一個(gè)指針,指向下一個(gè)節(jié)點(diǎn),這個(gè)對應(yīng)的是java.lang.ref.Reference#discovered這個(gè)成員變量。
public abstract class Reference<T> {
// 會被GC特殊對待
private T referent;
// reference被回收后,當(dāng)前Reference實(shí)例會被添加到這個(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線程
通過上文的討論,我們知道一個(gè)Reference實(shí)例化后狀態(tài)為Active,其引用的對象被回收后,垃圾回收器將其加入到pending-Reference鏈表,等待加入ReferenceQueue。這個(gè)過程是如何實(shí)現(xiàn)的呢?
這個(gè)過程不能對垃圾回收器產(chǎn)生影響,所以不能在垃圾回收線程中執(zhí)行,也就需要一個(gè)獨(dú)立的線程來負(fù)責(zé)。這個(gè)線程就是ReferenceHandler,它定義在Reference類中:
// 用于控制垃圾回收器操作與Pending狀態(tài)的Reference入隊(duì)操作不沖突執(zhí)行的全局鎖
// 垃圾回收器開始一輪垃圾回收前要獲取此鎖
// 所以所有占用這個(gè)鎖的代碼必須盡快完成,不能生成新對象,也不能調(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è)異常對象,
// 如果此時(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,會關(guān)聯(lián)ReferenceQueue.NULL,這里就不會進(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)先級
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
}
綜上,ReferenceHandler是一個(gè)最高優(yōu)先級的線程,其邏輯是從Pending-Reference鏈表中取出Reference,添加到其關(guān)聯(lián)的Reference-Queue中。

再來看些細(xì)節(jié)代碼:
ReferenceQueue VS Reference
Reference作為SoftReference,WeakReference,PhantomReference,F(xiàn)inalReference這幾個(gè)引用類型的父類。主要有兩個(gè)字段referent、queue,一個(gè)是指所引用的對象,一個(gè)是與之對應(yīng)的ReferenceQueue。Reference類有個(gè)構(gòu)造函數(shù) Reference(T referent, ReferenceQueue<? super T> queue),可以通過該構(gòu)造函數(shù)傳入與Reference相伴的ReferenceQueue。
ReferenceQueue本身提供隊(duì)列的功能,有入隊(duì)(enqueue)和出隊(duì)(poll,remove,其中remove阻塞等待提取隊(duì)列元素)。ReferenceQueue對象本身保存了一個(gè)Reference類型的head節(jié)點(diǎn),Reference封裝了next字段,這樣就是可以組成一個(gè)單向鏈表。同時(shí)ReferenceQueue提供了兩個(gè)靜態(tài)字段NULL,ENQUEUED
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
這兩個(gè)字段的主要功能:NULL是當(dāng)我們構(gòu)造Reference實(shí)例時(shí)queue傳入null時(shí),會默認(rèn)使用NULL,這樣在enqueue時(shí)判斷queue是否為NULL,如果為NULL直接返回,入隊(duì)失敗。ENQUEUED的作用是防止重復(fù)入隊(duì),reference后會把其queue字段賦值為ENQUEUED,當(dāng)再次入隊(duì)時(shí)會直接返回失敗。
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
Reference與ReferenceQueue之間是如何工作的呢?Reference里有個(gè)靜態(tài)字段pending,同時(shí)還通過靜態(tài)代碼塊啟動(dòng)了Reference-handler thread。當(dāng)一個(gè)Reference的referent被回收時(shí), 垃圾回收器會把reference添加到pending這個(gè)鏈表里,然后Reference-handler thread不斷的讀取pending中的reference,把它加入到對應(yīng)的ReferenceQueue中, 我們可以通過下面代碼塊來進(jìn)行把SoftReference,WeakReference,PhantomReference與ReferenceQueue聯(lián)合使用來驗(yàn)證這個(gè)機(jī)制。為了確保SoftReference在每次gc后,其引用的referent都被回收,我們需要加入-XX:SoftRefLRUPolicyMSPerMB=0參數(shù),
通過jstack命令可以看到對應(yīng)的Reference Handler thread
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007f8fb2836800 nid=0x2e03 in Object.wait() [0x000070000082b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000740008878> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000740008878> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
因此可以看出,當(dāng)reference與referenQueue聯(lián)合使用的主要作用就是當(dāng)reference指向的referent回收時(shí)(或者要被回收 如下文要講的Finalizer),提供一種通知機(jī)制,通過queue取到這些reference,來做額外的處理工作。當(dāng)然,如果我們不需要這種通知機(jī)制,我們就不用傳入額外的queue,默認(rèn)使用NULL queue就會入隊(duì)失敗。
SoftReference
根據(jù)上面我們講的對象可達(dá)性原理,我們把一個(gè)對象存在根對象對其有直接或間接的SoftReference,并沒有其他強(qiáng)引用路徑,我們把該對象成為softly-reachable對象。JVM保證在拋出OutOfMemoryError前會回收這些softly-reachable對象。JVM會根據(jù)當(dāng)前內(nèi)存的情況來決定是否回收softly-reachable對象,但只要referent有強(qiáng)引用存在,該referent就一定不會被清理,因此SoftReference適合用來實(shí)現(xiàn)memory-sensitive caches。軟引用的回收策略在不同的JVM實(shí)現(xiàn)會略有不同,javadoc中說明:
Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.
也就是說JVM不僅僅只會考慮當(dāng)前內(nèi)存情況,還會考慮軟引用所指向的referent最近使用情況和創(chuàng)建時(shí)間來綜合決定是否回收該referent。
Hotspot在gc時(shí)會根據(jù)兩個(gè)標(biāo)準(zhǔn)來回收:
- 根據(jù)SoftReference引用實(shí)例的timestamp(每次調(diào)用softReference.get()會自動(dòng)更新該字段,把最近一次垃圾回收時(shí)間賦值給timestamp,見源碼)
- 當(dāng)前JVM heap的內(nèi)存剩余(free_heap)情況
計(jì)算的規(guī)則是:
- free_heap 表示當(dāng)前堆剩余的內(nèi)存,單位是MB
- interval 表示最近一次GC's clock 和 當(dāng)前我們要判斷的softReference的timestamp 差值
- ms_per_mb is a constant number of milliseconds to keep around a SoftReference for each free megabyte in the heap(可以通過-XX:SoftRefLRUPolicyMSPerMB來設(shè)定)
那么判斷依據(jù)就是: interval <= free_heap * ms_per_mb,如果為true,則保留,false則進(jìn)行對象清除。_ ** SoftReferences will always be kept for at least one GC after their last access。**_ 因?yàn)?只要調(diào)用一次,那么clock和timestamp的值就會一樣,clock-timestamp則為0,一定小于等于free_heap * ms_per_mb。 OpenJDK的大概referencePolicy.cpp代碼是:
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");
}
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;
}
可見,SoftReference在一定程度上會影響JVM GC的,例如softly-reachable對應(yīng)的referent多次垃圾回收仍然不滿足釋放條件,那么它會停留在heap old區(qū),占據(jù)很大部分空間,在JVM沒有拋出OutOfMemoryError前,它有可能會導(dǎo)致頻繁的Full GC。
WeakReference
當(dāng)一個(gè)對象被WeakReference引用時(shí),處于weakly-reachable狀態(tài)時(shí),只要發(fā)生GC時(shí),就會被清除,同時(shí)會把WeakReference注冊到引用隊(duì)列中(如果存在的話)。 WeakReference不阻礙或影響它們對應(yīng)的referent被終結(jié)(finalized)和回收(reclaimed),因此,WeakReference經(jīng)常被用作實(shí)現(xiàn)規(guī)范映射(canonicalizing mappings)。相比SoftReference來說,WeakReference對JVM GC幾乎是沒有影響的。
下面我們舉個(gè)WeakReference應(yīng)用場景,JDK自帶的WeakHashMap,我們用下面的代碼來測試查看WeakHashMap在gc后的entry的情況,加入-verbose:gc運(yùn)行。
/**
* 加入下面參數(shù),觀察gc情況
* -verbose:gc
*/
public class WeakHashMapTest {
private static Map<String,byte[]> caches=new WeakHashMap<>();
public static void main(String[]args) throws InterruptedException {
for (int i=0;i<100000;i++){
caches.put(i+"",new byte[1024*1024*10]);
System.out.println("put num: " + i + " but caches size:" + caches.size());
}
}
}
運(yùn)行代碼我們可以看到,雖然我們不斷的往caches中put元素,但是caches size會伴隨每次gc又從0開始了。
WeakHashMap實(shí)現(xiàn)原理很簡單,它除了實(shí)現(xiàn)標(biāo)準(zhǔn)的Map接口,里面的機(jī)制也和HashMap的實(shí)現(xiàn)類似。從它entry子類中可以看出,它的key是用WeakReference包裹住的。當(dāng)這個(gè)key對象本身不再被使用時(shí),伴隨著GC的發(fā)生,會自動(dòng)把該key對應(yīng)的entry都在Map中清除掉。它為啥能夠自動(dòng)清除呢?這就是利用上面我們講的ReferenceQueue VS Reference的原理。WeakHashMap里聲明了一個(gè)queue,Entry繼承WeakReference,構(gòu)造函數(shù)中用key和queue關(guān)聯(lián)構(gòu)造一個(gè)weakReference,當(dāng)key不再被使用gc后會自動(dòng)把把key注冊到queue中:
/**
* Reference queue for cleared WeakEntries
*/
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the key.
*/
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
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關(guān)鍵的清理entry代碼:
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
這段代碼會在resize,getTable,size里執(zhí)行,清除失效的entry。
PhantomReference
PhantomReference 不同于WeakReference、SoftReference,它存在的意義不是為了獲取referent,因?yàn)槟阋灿肋h(yuǎn)獲取不到,因?yàn)樗膅et如下
public T get() {
return null;
}
PhantomReference主要作為其指向的referent被回收時(shí)的一種通知機(jī)制,它就是利用上文講到的ReferenceQueue實(shí)現(xiàn)的。當(dāng)referent被gc回收時(shí),JVM自動(dòng)把PhantomReference對象(reference)本身加入到ReferenceQueue中,像發(fā)出信號通知一樣,表明該reference指向的referent被回收。然后可以通過去queue中取到reference,此時(shí)說明其指向的referent已經(jīng)被回收,可以通過這個(gè)通知機(jī)制來做額外的清場工作。 因此有些情況可以用PhantomReference 代替finalize(),做資源釋放更明智。
下面舉個(gè)例子,用PhantomReference來自動(dòng)關(guān)閉文件流。
public class ResourcePhantomReference<T> extends PhantomReference<T> {
private List<Closeable> closeables;
public ResourcePhantomReference(T referent, ReferenceQueue<? super T> q, List<Closeable> resource) {
super(referent, q);
closeables = resource;
}
public void cleanUp() {
if (closeables == null || closeables.size() == 0)
return;
for (Closeable closeable : closeables) {
try {
closeable.close();
System.out.println("clean up:"+closeable);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class ResourceCloseDeamon extends Thread {
private static ReferenceQueue QUEUE = new ReferenceQueue();
//保持對reference的引用,防止reference本身被回收
private static List<Reference> references=new ArrayList<>();
@Override
public void run() {
this.setName("ResourceCloseDeamon");
while (true) {
try {
ResourcePhantomReference reference = (ResourcePhantomReference) QUEUE.remove();
reference.cleanUp();
references.remove(reference);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void register(Object referent, List<Closeable> closeables) {
references.add(new ResourcePhantomReference(referent,QUEUE,closeables));
}
}
public class FileOperation {
private FileOutputStream outputStream;
private FileInputStream inputStream;
public FileOperation(FileInputStream inputStream, FileOutputStream outputStream) {
this.outputStream = outputStream;
this.inputStream = inputStream;
}
public void operate() {
try {
inputStream.getChannel().transferTo(0, inputStream.getChannel().size(), outputStream.getChannel());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class PhantomTest {
public static void main(String[] args) throws Exception {
//打開回收
ResourceCloseDeamon deamon = new ResourceCloseDeamon();
deamon.setDaemon(true);
deamon.start();
// touch a.txt b.txt
// echo "hello" > a.txt
//保留對象,防止gc把stream回收掉,其不到演示效果
List<Closeable> all=new ArrayList<>();
FileInputStream inputStream;
FileOutputStream outputStream;
for (int i = 0; i < 100000; i++) {
inputStream = new FileInputStream("/Users/robin/a.txt");
outputStream = new FileOutputStream("/Users/robin/b.txt");
FileOperation operation = new FileOperation(inputStream, outputStream);
operation.operate();
TimeUnit.MILLISECONDS.sleep(100);
List<Closeable>closeables=new ArrayList<>();
closeables.add(inputStream);
closeables.add(outputStream);
all.addAll(closeables);
ResourceCloseDeamon.register(operation,closeables);
//用下面命令查看文件句柄,如果把上面register注釋掉,就會發(fā)現(xiàn)句柄數(shù)量不斷上升
//jps | grep PhantomTest | awk '{print $1}' |head -1 | xargs lsof -p | grep /User/robin
System.gc();
}
}
運(yùn)行上面的代碼,通過jps | grep PhantomTest | awk '{print $1}' |head -1 | xargs lsof -p | grep /User/robin | wc -l 可以看到句柄沒有上升,而去掉ResourceCloseDeamon.register(operation,closeables);時(shí),句柄就不會被釋放。
PhantomReference使用時(shí)一定要傳一個(gè)referenceQueue,當(dāng)然也可以傳null,但是這樣就毫無意義了。因?yàn)镻hantomReference的get結(jié)果為null,如果在把queue設(shè)為null,那么在其指向的referent被回收時(shí),reference本身將永遠(yuǎn)不會可能被加入隊(duì)列中.
FinalReference
FinalReference 引用類型主要是為虛擬機(jī)提供的,提供 _ 對象被gc前需要執(zhí)行finalize方法的對象_ 的機(jī)制。
FinalReference 很簡單就是extend Reference類,沒有做其他邏輯,只是把訪問權(quán)限改為package,因此我們是無法直接使用的。Finalizer類是我們要講的重點(diǎn),它繼承了FinalReference,并且是final 類型的。Finalize實(shí)現(xiàn)很簡單,也是利用上面我們講的ReferenceQueue VS Reference機(jī)制。
FinalizerThread
Finalizer靜態(tài)代碼塊里啟動(dòng)了一個(gè)deamon線程,我們通過jstack命令查看線程時(shí),總會看到一個(gè)Finalizer線程,就是這個(gè)原因:
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
FinalizerThread run方法是不斷的從queue中去取Finalizer類型的reference,然后執(zhí)行runFinalizer釋放方法。
public void run() {
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
runFinalizer方法體,執(zhí)行事發(fā)邏輯,可以看出如果finalize方法中拋出異常會被直接吃掉:
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
介紹完上面的處理機(jī)制,那么剩下的就是入queue的事情,就是哪些類對象需要入隊(duì),何時(shí)入隊(duì).
哪些類對象是Finalizer reference類型的referent呢
只要類覆寫了Object 上的finalize方法,方法體非空。那么這個(gè)類的實(shí)例都會被Finalizer引用類型引用的。下文中我們簡稱Finalizer 型的referent為finalizee。
何時(shí)調(diào)用Finalizer.register生成一個(gè)Finalizer類型的reference
Finalizer的構(gòu)造函數(shù)是private的,也就是不能通過new 來生成一個(gè)Fianlizer reference。只能通過靜態(tài)的register方法來生成。同時(shí)Finalizer有個(gè)靜態(tài)字段unfinalized,維護(hù)了一個(gè)未執(zhí)行finalize方法的reference列表,在構(gòu)造函數(shù)中通過add()方法把Finalizer引用本身加入到unfinalized列表中,同時(shí)關(guān)聯(lián)finalizee和queue,實(shí)現(xiàn)通知機(jī)制。維護(hù)靜態(tài)字段unfinalized的目的是為了一直保持對未未執(zhí)行finalize方法的reference的強(qiáng)引用,防止被gc回收掉。
private static Finalizer unfinalized = null;
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
那么register是被VM何時(shí)調(diào)用的呢?JVM通過VM參數(shù) RegisterFinalizersAtInit 的值來確定何時(shí)調(diào)用register,RegisterFinalizersAtInit默認(rèn)為true,則會在構(gòu)造函數(shù)返回之前調(diào)用call_register_finalizer方法。
void Parse::return_current(Node* value) {
if (RegisterFinalizersAtInit &&
method()->intrinsic_id() == vmIntrinsics::_Object_init) {
call_register_finalizer();
}
..............
}
如果通過-XX:-RegisterFinalizersAtInit 設(shè)為false,則會在對象空間分配好之后就調(diào)用call_register_finalizer
nstanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
int size = size_helper(); // Query before forming handle.
KlassHandle h_k(THREAD, this);
instanceOop i;
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
何時(shí)入queue
當(dāng)一個(gè)finalizee 只剩Finalizer引用,沒有其他引用時(shí),需要被回收了,GC就會把該finalizee對應(yīng)的reference放到Finalizer的refereneQueue中,等待FinalizerThread來執(zhí)行finalizee的finalize方法,然后finalizee對象才能被GC回收。
Finalizer問題
- finalizee對象在finalize重新被賦給一個(gè)強(qiáng)引用復(fù)活,那么下次GC前會不會被再次執(zhí)行finalize方法?
答案是不會的,runFinalizer中會把該finalizee對應(yīng)的Finalizer引用從unfinalized隊(duì)列中移除,第二次執(zhí)行的時(shí)會通過hasBeenFinalized方法判斷,保證不會被重復(fù)執(zhí)行。
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
finalizee至少兩次GC回收才可能被回收?
第一次GC把finalizee對應(yīng)的Finalizer reference加入referenceQueue等待FinalizerThread來執(zhí)行finalize方法。第二次GC才有可能釋放finalizee對象本身,前提是FinalizerThread已經(jīng)執(zhí)行完finalize方法了,并把Finalizer reference從Finalizer靜態(tài)unfinalized鏈表中剔除,因?yàn)檫@個(gè)鏈表和Finalizer reference對finalizee構(gòu)成的是一個(gè)強(qiáng)引用。Finalizer 機(jī)制導(dǎo)致JVM Full GC 頻繁,stop-the-world延長?
因?yàn)槿绻鹒inalizee上的finalize方法體執(zhí)行過程耗時(shí)比較長,會導(dǎo)致對象一直堆積,多次GC仍不能釋放,沖進(jìn)old區(qū),造成Old區(qū)GC過程延長,暫停時(shí)間增加,可能頻繁觸發(fā)Full GC。
小結(jié)
通過對SoftReference,WeakReference,PhantomReference,F(xiàn)inalReference 的介紹,可以看出JDK提供這些類型的reference 主要是用來和GC交互的,根據(jù)reference的不同,讓JVM采用不同策略來進(jìn)行對對象的回收(reclaim)。softly-reachable的referent在保證在OutOfMemoryError之前回收對象,weakly-reachable的referent在發(fā)生GC時(shí)就會被回收,finalizer型的reference 主要提供GC前對referent進(jìn)行finalize執(zhí)行機(jī)制。同時(shí)這些reference和referenceQueue在一起提供通知機(jī)制,PhantomReference的作用就是僅僅就是提供對象回收通知機(jī)制,F(xiàn)inalizer借助這種機(jī)制實(shí)現(xiàn)referent的finalize執(zhí)行,SoftReference、WeakReference也可以配合referenceQueue使用,實(shí)現(xiàn)對象回收通知機(jī)制。