1 Java中的四種引用
在Java中提供了四個(gè)級(jí)別的引用:強(qiáng)引用返十,軟引用,弱引用和虛引用吸奴。在這四個(gè)引用類型中,只有強(qiáng)引用FinalReference類是包內(nèi)可見(jiàn),其他三種引用類型均為public则奥,可以在應(yīng)用程序中直接使用考润。引用類型的類結(jié)構(gòu)如圖所示。
1.1 強(qiáng)引用
Java中的強(qiáng)引用指的是逞度,程序中有直接可達(dá)的引用额划。如
Object obj = new Object();
強(qiáng)引用的特點(diǎn):
- 強(qiáng)引用可以直接訪問(wèn)目標(biāo)對(duì)象妙啃。
- 強(qiáng)引用所指向的對(duì)象在任何時(shí)候都不會(huì)被系統(tǒng)回收档泽。JVM寧愿拋出OOM異常,也不會(huì)回收強(qiáng)引用所指向的對(duì)象揖赴。
- 強(qiáng)引用可能導(dǎo)致內(nèi)存泄漏馆匿。
1.2 軟引用
軟引用是除了強(qiáng)引用外,最強(qiáng)的引用類型燥滑〗ケ保可以通過(guò)java.lang.ref.SoftReference使用軟引用。
SoftReference<Object> softRef = new SoftReference<Object>(new Object());
軟引用的特點(diǎn):
1.軟引用的對(duì)象铭拧,只有在內(nèi)存不足的時(shí)候(拋出OOM異常前)赃蛛,垃圾收集器會(huì)決定回收該軟引用所指向的對(duì)象。軟引用通常用于實(shí)現(xiàn)內(nèi)存敏感的緩存搀菩。
2.在垃圾線程對(duì) 這個(gè)Java對(duì)象回收前呕臂,SoftReference類所提供的get()方法返回Java對(duì)象的強(qiáng)引用。一旦垃圾線程回收該Java對(duì)象之后肪跋,get()方法將返回null歧蒋。
1.3 弱引用
弱引用是一種比軟引用較弱的引用類型≈菁龋可以用java.lang.ref.WeakReference實(shí)例來(lái)保存對(duì)一個(gè)Java對(duì)象的弱引用谜洽。
WeakReference<Object> weakRef = new WeakReference<Object>(new Object());
軟引用的特點(diǎn):
1.不管內(nèi)存是否足夠,只要被垃圾收集器發(fā)現(xiàn)吴叶,該引用的對(duì)象就會(huì)被回收
1.4 虛引用
虛引用是所有類型中最弱的一個(gè)阐虚。一個(gè)持有虛引用的對(duì)象,和沒(méi)有引用幾乎是一樣的蚌卤,可以用java.lang.ref.WeakReference實(shí)例來(lái)保存對(duì)一個(gè)Java對(duì)象的弱引用实束。
Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<Object>(obj, refQueue);
虛引用的特點(diǎn):
1.隨時(shí)可能被垃圾回收器回收。
2.當(dāng)試圖通過(guò)虛引用的get()方法取得強(qiáng)引用時(shí)造寝,總是返回null磕洪。
3.虛引用必須和引用隊(duì)列一起使用。
當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)诫龙,如果發(fā)現(xiàn)它還有虛引用析显,就會(huì)在垃圾回收后,銷毀這個(gè)對(duì)象签赃,將這個(gè)虛引用加入引用隊(duì)列谷异。程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入了虛引用分尸,來(lái)了解被引用的對(duì)象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列歹嘹,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)箩绍。
2 ReferenceQueue
引用隊(duì)列,當(dāng)檢測(cè)到對(duì)象的可到達(dá)性更改時(shí)尺上,垃圾回收器將已注冊(cè)的引用對(duì)象添加到隊(duì)列中材蛛,ReferenceQueue實(shí)現(xiàn)了入隊(duì)(enqueue)和出隊(duì)(poll),還有remove操作怎抛,內(nèi)部元素head就是泛型的Reference卑吭。
//創(chuàng)建一個(gè)引用隊(duì)列
ReferenceQueue queue = new ReferenceQueue();
// 創(chuàng)建弱引用,此時(shí)狀態(tài)為Active马绝,并且Reference.pending為空豆赏,當(dāng)前Reference.queue = 上面創(chuàng)建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 當(dāng)GC執(zhí)行后富稻,由于是弱引用掷邦,所以回收該object對(duì)象,并且置于pending上椭赋,此時(shí)reference的狀態(tài)為PENDING
System.gc();
/* ReferenceHandler從pending中取下該元素抚岗,并且將該元素放入到queue中,此時(shí)Reference狀態(tài)為ENQUEUED纹份,Reference.queue = ReferenceENQUEUED */
/* 當(dāng)從queue里面取出該元素苟跪,則變?yōu)镮NACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);
3 源碼分析
3.1Reference定義的4種狀態(tài)
Reference類首先把內(nèi)存分為4種狀態(tài)Active蔓涧,Pending件已,Enqueued,Inactive元暴。
- Active庞萍,一般來(lái)說(shuō)內(nèi)存一開(kāi)始被分配的狀態(tài)都是 Active燎悍,
- Pending 大概是指快要被放進(jìn)隊(duì)列的對(duì)象,也就是馬上要回收的對(duì)象,
- Enqueued 就是對(duì)象的內(nèi)存已經(jīng)被回收了裙盾,我們已經(jīng)把這個(gè)對(duì)象放入到一個(gè)隊(duì)列中伙窃,方便以后我們查詢某個(gè)對(duì)象是否被回收择膝,
- Inactive就是最終的狀態(tài)奢米,不能再變?yōu)槠渌鼱顟B(tài)。
3.2 Reference成員變量
// 用于保存對(duì)象的引用讶迁,GC會(huì)根據(jù)不同Reference來(lái)特別對(duì)待
private T referent;
// 如果需要通知機(jī)制连茧,則保存的對(duì)對(duì)應(yīng)的隊(duì)列
ReferenceQueue<? super T> queue;
/* 這個(gè)用于實(shí)現(xiàn)一個(gè)單向循環(huán)鏈表,用以將保存需要由ReferenceHandler處理的引用 */
Reference next;
static private class Lock { };
// 鎖,用于同步pending隊(duì)列的進(jìn)隊(duì)和出隊(duì)
private static Lock lock = new Lock();
transient private Reference<T> discovered; /* used by VM */
// 此屬性保存一個(gè)PENDING的隊(duì)列啸驯,配合上述next一起使用
private static Reference pending = null;
- referent表示其引用的對(duì)象客扎,即在構(gòu)造的時(shí)候需要被包裝在其中的對(duì)象。
- queue 是對(duì)象即將被回收時(shí)所要通知的隊(duì)列罚斗。當(dāng)對(duì)象即將被回收時(shí)徙鱼,整個(gè)reference對(duì)象,而不僅僅是被回收的對(duì)象针姿,會(huì)被放到queue 里面袱吆,然后外部程序即可通過(guò)監(jiān)控這個(gè) queue 即可拿到相應(yīng)的數(shù)據(jù)了。
- next 即當(dāng)前引用節(jié)點(diǎn)所存儲(chǔ)的下一個(gè)即將被處理的節(jié)點(diǎn)搓幌。但 next 僅在放到queue中才會(huì)有意義杆故,因?yàn)橹挥性趀nqueue的時(shí)候,會(huì)將next設(shè)置為下一個(gè)要處理的Reference對(duì)象溉愁。為了描述相應(yīng)的狀態(tài)值,在放到隊(duì)列當(dāng)中后饲趋,其queue就不會(huì)再引用這個(gè)隊(duì)列了拐揭。而是引用一個(gè)特殊的 ENQUEUED(內(nèi)部定義的一個(gè)空隊(duì)列)。因?yàn)橐呀?jīng)放到隊(duì)列當(dāng)中奕塑,并且不會(huì)再次放到隊(duì)列當(dāng)中堂污。
- discovered 表示要處理的對(duì)象的下一個(gè)對(duì)象。即可以理解要處理的對(duì)象也是一個(gè)鏈表龄砰,通過(guò)discovered進(jìn)行排隊(duì)盟猖,這邊只需要不停地拿到pending,然后再通過(guò)discovered 不斷地拿到下一個(gè)對(duì)象賦值給pending即可换棚,直到取到了最有一個(gè)式镐。它是被JVM 使用的。
- pending 是等待被入隊(duì)的引用列表固蚤。JVM 收集器會(huì)添加引用到這個(gè)列表娘汞,直到Reference-handler線程移除了它們。這個(gè)列表使用 discovered 字段來(lái)連接它下一個(gè)元素(即 pending 的下一個(gè)元素就是discovered對(duì)象夕玩。r = pending; pending = r.discovered)你弦。
3.3 ReferenceQueue成員變量
// 用于標(biāo)識(shí)沒(méi)有注冊(cè)Queue
static ReferenceQueue NULL = new Null();
// 用于標(biāo)識(shí)已經(jīng)處于對(duì)應(yīng)的Queue中
static ReferenceQueue ENQUEUED = new Null();
static private class Lock { };
/* 互斥鎖,用于同步ReferenceHandler的enqueue和用戶線程操作的remove和poll出隊(duì)操作 */
private Lock lock = new Lock();
// 隊(duì)列
private volatile Reference<? extends T> head = null;
// 隊(duì)列中的元素個(gè)數(shù)
private long queueLength = 0;
3.4 對(duì)象的入隊(duì)過(guò)程
當(dāng) Reference 類被加載的時(shí)候燎孟,會(huì)執(zhí)行靜態(tài)代碼塊禽作。在靜態(tài)代碼塊里面,會(huì)啟動(dòng) ReferenceHandler 線程,并設(shè)置線程的級(jí)別為最大級(jí)別揩页, Thread.MAX_PRIORITY旷偿。
檢查 pending 是否為 null,如果pending不為 null,則將 pending 進(jìn)行 enqueue狸捅,否則線程進(jìn)入 wait 狀態(tài)衷蜓。
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) {
// 檢查 pending 是否為 null,不為 null尘喝,制定 pending enqueue
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else { // 為 null磁浇。等待
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
}
/******************************ReferenceQueue*************************/
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;
// queue 為 null 或者 queue 已經(jīng)被回收了,直接返回
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 將 Reference 的狀態(tài)置為 Enqueued朽褪,表示已經(jīng)被回收
r.queue = ENQUEUED;
// 接著置吓,將 Reference 插入到鏈表
// 判斷當(dāng)前鏈表是否為 null,不為 null缔赠,將 r.next 指向 head衍锚,為 null,head 直接指向 r
r.next = (head == null) ? r : head;
// head 指針指向 r
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
注意:
JVM在GC時(shí)會(huì)把回收了內(nèi)存的對(duì)象的'Reference'通過(guò)'discovered'連接成鏈表嗤堰。
‘pending’屬性相對(duì)于'discovered'鏈表上面的指針戴质,每次把當(dāng)前的元素放入引用隊(duì)列,然后指向鏈表的下一個(gè)元素踢匣。
'Reference'的'next'屬于用于指向一個(gè)已經(jīng)在隊(duì)列中被回收的對(duì)象告匠。
4 Object的finalize()方法原理
4.1 finalize()方法簡(jiǎn)介
- 如果一個(gè)類實(shí)現(xiàn)了finalize方法,那么GC在回收這個(gè)對(duì)象之前离唬,會(huì)將finalize方法進(jìn)行調(diào)用后专。finalize的一般約定是,jvm虛擬機(jī)已經(jīng)確定沒(méi)有任何引用或者線程訪問(wèn)此對(duì)象输莺,就會(huì)調(diào)用這個(gè)finalize方法戚哎。在finalize方法中可以進(jìn)行任何操作,包括該對(duì)象再次對(duì)其他線程進(jìn)行調(diào)用嫂用。這個(gè)方法的目的是在gc回收對(duì)象之前型凳,再次對(duì)之前未關(guān)閉的資源進(jìn)行回收。如IO操作中的連接等尸折。
- java虛擬機(jī)并不保證哪個(gè)線程會(huì)具體調(diào)用finalize方法啰脚,但是可以保證調(diào)用finalize方法的時(shí)候不會(huì)有任何用戶可見(jiàn)的同步鎖。如果finalize方法中出現(xiàn)任何異常实夹,則這些異常會(huì)被忽略橄浓,且finalize方法會(huì)終止。
- 在一個(gè)對(duì)象調(diào)用finalize方法之后亮航,在jvm確認(rèn)這個(gè)對(duì)象沒(méi)有任何其他對(duì)象能訪問(wèn)之前,也就是說(shuō)jvm確認(rèn)這個(gè)對(duì)象不是垃圾之前缴淋。finalize方法不會(huì)執(zhí)行泄朴。
- finalize 方法只會(huì)被虛擬機(jī)執(zhí)行一次。
- finalize方法中的異常會(huì)被忽略祖灰,之后finalize方法會(huì)終止。
4.2 FinalReference
FinalReference的實(shí)現(xiàn)非常簡(jiǎn)單局扶,這個(gè)類不是public的,其作用域在protected三妈,也就是說(shuō)除了java.lang.ref包中的類能訪問(wèn)之外,不能在任何自定義的代碼中調(diào)用畴蒲。這也說(shuō)明這是一個(gè)jvm才能訪問(wèn)的類。
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
4.3 Finalizer
Finalizer是finalReference的子類模燥,對(duì)queue和lock進(jìn)行了重寫(xiě)。Finalizer也是protected作用域软驰,另外通過(guò)final修飾涧窒。不可被繼承。
//final修飾的類不可被繼承
final class Finalizer extends FinalReference<Object> {
//重寫(xiě)了queue屬性锭亏,F(xiàn)inalizer必須使用ReferenceQueue,因此一開(kāi)始就對(duì)queue進(jìn)行了實(shí)例化
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
//重載了鎖
private static final Object lock = new Object();
//鏈表指針,F(xiàn)inalizer是個(gè)雙向鏈表
private Finalizer
next = null,
prev = null;
}
4.4 FinalizerThread
jvm在注冊(cè)的時(shí)候硬鞍,實(shí)際上就是創(chuàng)建了一個(gè)Finalizer的鏈表慧瘤。在GC的時(shí)候,如果發(fā)現(xiàn)對(duì)象只被Finalizer引用固该,則說(shuō)明這個(gè)對(duì)象可以被回收了锅减。那么就將其從引用對(duì)象鏈中取出,放入ReferenceQueue中伐坏。之后通知Finalizer Thread線程去消費(fèi)怔匣。之后去調(diào)用finalize方法。
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
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
}
}
}
}
/*********************************Finalizer****************************/
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();
}
流程:
- 在創(chuàng)建對(duì)象的時(shí)候桦沉,如果重寫(xiě)了finalize方法每瞒,jvm就會(huì)同時(shí)創(chuàng)建一個(gè)Finalizer對(duì)象。
- 所有的Finalizer對(duì)象構(gòu)成一個(gè)雙向鏈表
- 所有的Finalizer對(duì)象都有一個(gè)名為queue的ReferenceQueue隊(duì)列
- GC在執(zhí)行標(biāo)記的最后階段纯露,會(huì)把Finalizer的對(duì)象加入到Reference的pending-list 鏈表中剿骨。
- ReferenceHandler會(huì)將pending-list中的對(duì)象取出,放置到這個(gè)ReferenceQueue中埠褪。對(duì)于finalReference而言浓利,這個(gè)queue即使初始化的時(shí)候創(chuàng)建的static的queue挤庇。對(duì)于所有的FinalReference全局只有一個(gè)ReferenceQueue。
- Finalizer中有一個(gè)專門(mén)的守護(hù)線程 Finalizer Thread,這個(gè)線程中有一個(gè)死循環(huán)贷掖,專門(mén)從queue中取出對(duì)象嫡秕,并執(zhí)行Finalizer中引用對(duì)象的finalize方法。之后從隊(duì)列中移除苹威,強(qiáng)引用消除昆咽。
- 再次GC 這個(gè)Finalizer的對(duì)象沒(méi)有任何引用,因此可能被回收掉屠升。
java虛引用的使用說(shuō)明 - followus - 博客園
Java Reference 源碼分析 - Jabnih - 博客園
java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue_gdutxiaoxu的博客(微信公眾號(hào) stormjun94)-CSDN博客
java中的reference(三): FinalReference和Finalizer的源碼分析_dhaibo1986的專欄-CSDN博客