目錄
一、Reference
1、SoftReference / WeakReference / PhantomReference
2、定義
3、ReferenceHandler
4、Cleaner
二、ReferenceQueue
1、定義
2、 enqueue / reallyPoll
3、poll / remove
4、forEach
三、Finalizer
1、定義
2、register
3、FinalizerThread
4、runFinalization / runAllFinalizers
5、InstanceKlass::register_finalizer
一、Reference
Reference是所有表示對象引用的類的抽象基類,定義了公共的方法,Reference所引用的對象稱為referent。因為Reference的實作與JVM的垃圾回收機制是強相關的,是以不建議直接繼承Reference,避免改變Reference的關鍵實作,其類繼承關系如下圖:
下面逐一講解各類的使用和源碼實作細節。
1、SoftReference / WeakReference / PhantomReference
這三個對象的表示引用是越來越弱的,SoftReference通常用來實作記憶體敏感的緩存,當記憶體不足的時候,為了擷取可用記憶體空間會回收SoftReference所引用的對象,SoftReference本身會被放到建立時傳入的ReferenceQueue中,JVM保證在抛出OutOfMemoryError異常前,清除所有的SoftReference。SoftReference增加了兩個屬性:
- static long clock; //由垃圾回收器維護的表示時間的字段
- long timestamp; //用來儲存目前的clock
SoftReference改寫了原來的構造方法和get方法的實作,增加了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;
}
WeakReference通常用來實作類似WeakMap的特殊Map,不能阻止key或者value被垃圾回收了,當垃圾回收器發現一個對象隻是被WeakReference所引用就會回收掉該對象,并将關聯的WeakReference加入到建立時傳入的ReferenceQueue中。WeakReference沒有新增屬性,隻是定義了自己的構造方法而已,如下:
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
PhantomReference通常用來實作一種更流暢的類似Object.finalize的清理功能,與SoftReference和WeakReference不同的是,PhantomReference的get方法永遠傳回null,為了保證其所引用的對象一直處于可被回收的狀态,并且當垃圾回收器判斷某個對象隻是被PhantomReference所引用,然後将PhantomReference加入到建立時傳入的ReferenceQueue中,這個時候垃圾回收器不會立即回收掉PhantomReference所引用的對象,而是等到所有的PhantomReference對象都放到ReferenceQueue中或者PhantomReference對象本身變得不可達。WeakReference也沒有新增屬性,改寫了原有的get方法實作,永遠傳回null,如下:
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
這三個類的代碼調用都大同小異,具體可以參考《java8 WeakHashMap接口實作源碼解析》中WeakReference的應用。
2、定義
Reference包含的屬性如下:
- T referent; //所引用的對象
- volatile ReferenceQueue<? super T> queue; //當所引用的對象referent被清理時用來儲存Reference的隊列,調用方可以通過ReferenceQueue的poll方法擷取隊列中的Reference執行個體,進而知道哪些referent對象被回收掉了
- volatile Reference next; //ReferenceQueue使用,通過next屬性将所有加入到ReferenceQueue中的Reference執行個體構成一個連結清單
- transient Reference<T> discovered; //JVM使用的,用于将所有的Pending狀态的Reference執行個體構成一個連結清單
- static Lock lock = new Lock(); //用來修改Pending狀态的Reference執行個體連結清單的鎖
- static Reference<Object> pending = null; //Pending狀态的Reference執行個體連結清單的連結清單頭元素,垃圾回收器發現某個對象隻有Reference執行個體引用,就會把Reference對象加入到這個連結清單中,而ReferenceHandler Thread不斷從這個連結清單中移除元素,将其加入到Reference執行個體建立時傳入的ReferenceQueue隊列中
Reference定義了四種内部狀态:
- Active 新建立的Reference執行個體就是Active狀态,當垃圾回收器發現所引用的對象referent的可達狀态發生變更了,可能将Reference執行個體的狀态改成Pending或者Inactive,取決于Reference執行個體建立時是否傳入ReferenceQueue執行個體引用。如果是從Active改成Pending,則垃圾回收器還會将該Reference執行個體加入到pending屬性對應的Reference清單中
- Pending pending屬性對應的Reference清單中的Reference執行個體的狀态都是Pending,等待ReferenceHandler Thread将這些執行個體加入到queue隊列中
- Enqueued queue屬性對應的ReferenceQueue隊列中的Reference執行個體的狀态都是Enqueued,當執行個體從ReferenceQueue隊列中移除就會變成Inactive。如果Reference執行個體在建立時沒有傳入ReferenceQueue,則永遠不會處于Enqueued狀态。
- Inactive 變成Inactive以後,狀态就不會再變更,等待垃圾回收器回收掉該執行個體
在不同的狀态下,queue和next屬性的指派會發生變更,如下:
- Active queue就是Reference執行個體建立時傳入的ReferenceQueue引用,如果沒有傳入或者傳入的是null,則為ReferenceQueue.NULL;此時next屬性為null。
- Pending queue就是Reference執行個體建立時傳入的ReferenceQueue引用,next屬性是this
- Enqueued queue就是ReferenceQueue.ENQUEUED,next屬性就是隊列中的下一個元素,如果目前Reference執行個體就是最後一個,則是this
- Inactive queue就是ReferenceQueue.NULL,next屬性就是this
Reference定義的方法比較簡單,如下:
其中get方法傳回所引用的對象referent,clear方法用于将referent置為null,enqueue方法用于将目前Reference執行個體加入到建立時傳入的queue隊列中,isEnqueued方法判斷目前Reference執行個體是否已加入queue隊列中,tryHandlePending方法是ReferenceHandler Thread調用的用于處理Pending狀态的Reference執行個體的核心方法,是包級通路的。重點關注ReferenceHandler的實作。
3、ReferenceHandler
ReferenceHandler繼承自Thread,表示一個不斷将Pending狀态的Reference執行個體放入該執行個體建立時傳入的ReferenceQueue執行個體中,所有處于Pending狀态的Reference執行個體通過discovered執行個體屬性構成連結清單,連結清單頭就是Reference類的靜态屬性pending,在周遊連結清單時,如果連結清單為空則通過lock.wait()的方式等待;如果周遊的Reference執行個體是Cleaner,則調用其clean方法,用于清理資源清理。其實作如下:
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
//預先加載并初始化兩個類
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
//while true不斷執行
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
//注意lock和pending都是靜态屬性
synchronized (lock) {
if (pending != null) {
//如果存在待處理的Reference執行個體
r = pending;
//Cleaner是PhantomReference的子類
c = r instanceof Cleaner ? (Cleaner) r : null;
//通過discovered屬性将所有Pending的Reference執行個體構成一個連結清單
//擷取下一個處于Pending的Reference執行個體
pending = r.discovered;
//discovered屬性置為空
r.discovered = null;
} else {
//waitForNotify預設為true,阻塞目前線程直到其他線程喚醒
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
//yield方法會讓出目前線程的CPU處理時間,讓垃圾回收線程擷取更多的CPU時間,加速垃圾回收
Thread.yield();
return true;
} catch (InterruptedException x) {
// retry
return true;
}
if (c != null) {
//pending屬性是Cleaner,執行清理
c.clean();
return true;
}
//pending屬性不是Cleaner
//Pending狀态下,r.queue就是最初r建立時傳入的ReferenceQueue引用
ReferenceQueue<? super Object> q = r.queue;
//将r加入到queue 隊列中
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
ReferenceHandler的啟動是通過靜态static塊完成的,如下:
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
//往上周遊找到最初的父線程組
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
//建立ReferenceHandler線程,并啟動
Thread handler = new ReferenceHandler(tg, "Reference Handler");
handler.setPriority(Thread.MAX_PRIORITY); //最高優先級
handler.setDaemon(true); //背景線程
handler.start();
//允許通路SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
4、Cleaner
Cleaner繼承自PhantomReference,其源碼可以參考OpenJDK jdk\src\share\classes\sun\misc\Cleaner.java。Cleaner表示一種更輕量更健壯的資源清理方式,相對于Object的finalization機制,輕量是因為Cleaner不是JVM建立的,不需要借助JNI調用建立,執行資源清理的代碼是ReferenceHandler Thread調用的而非finalizer Thread;健壯是因為Cleaner繼承自PhantomReference,是最弱的一種引用類型,可以避免惡心的順序問題。Cleaner封裝了執行資源清理任務的邏輯,具體的資源清理邏輯通過建立Cleaner時的方法入參Runnable方法指定,Cleaner保證執行資源清理任務是線程安全的,即會捕獲所有的異常,且保證隻執行一次。一旦垃圾回收器發現Cleaner執行個體是phantom-reachable,即沒有其他執行個體強引用該執行個體,垃圾回收器就會把Cleaner執行個體加入到Reference的pending隊列中,由ReferenceHandler Thread負責調用其clean方法執行資源清理動作。Cleaner并不能完全替代Object的finalization機制,使用Cleaner時要求其資源清理邏輯比較簡單,否則容易阻塞ReferenceHandler Thread,阻塞其他的資源清理任務執行。其實作如下:
public class Cleaner
extends PhantomReference<Object>
{
// 因為PhantomReference的構造方法要求必須傳入ReferenceQueue參數,是以這裡聲明了一個,但是實際上并不會往裡面添加Cleaner執行個體
//因為ReferenceHandler Thread會直接調用Cleaner執行個體的clean方法,不會将其加入到dummyQueue隊列中
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
//Cleaner連結清單的連結清單頭
static private Cleaner first = null;
private Cleaner
next = null,
prev = null;
//add方法将cl加入到連結清單的頭部
private static synchronized Cleaner add(Cleaner cl) {
if (first != null) {
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
//remove方法将cl從連結清單中移除
private static synchronized boolean remove(Cleaner cl) {
//說明cl已經從連結清單移除了,不需要再處理
if (cl.next == cl)
return false;
// Update list
if (first == cl) {
if (cl.next != null)
first = cl.next;
else
first = cl.prev;
}
if (cl.next != null)
cl.next.prev = cl.prev;
if (cl.prev != null)
cl.prev.next = cl.next;
//從連結清單移除後會将cl的prev和next都指向它自己
cl.next = cl;
cl.prev = cl;
return true;
}
private final Runnable thunk;
private Cleaner(Object referent, Runnable thunk) {
super(referent, dummyQueue);
this.thunk = thunk;
}
/**
* 核心入口方法,建立Cleaner,thunk就是具體的執行資源清理的邏輯
*/
public static Cleaner create(Object ob, Runnable thunk) {
if (thunk == null)
return null;
return add(new Cleaner(ob, thunk));
}
/**
* Runs this cleaner, if it has not been run before.
*/
public void clean() {
//首先從隊列移除目前Cleaner執行個體
if (!remove(this))
return;
try {
//執行資源清理
thunk.run();
} catch (final Throwable x) {
//捕獲所有異常,列印err日志并退出
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通過prev,next屬性内部維護了一個雙向連結清單,其中靜态屬性first就是連結清單頭,即所有的Cleaner執行個體通過連結清單維護着引用關系,但是這種引用是phantom-reachable的,一旦某個Cleaner執行個體沒有強引用則會被垃圾回收器加入到Reference的pending隊列中,等待被處理。Cleaner的核心方法有兩個,一是建立Cleaner執行個體的create方法,該方法會将建立的執行個體加入到連結清單中;另外一個是執行資源清理的clean方法,該方法将目前執行個體從連結清單中移除,然後執行Cleaner執行個體建立時傳入的thunk,如果出現異常則列印err日志并導緻JVM程序終止。
可以參考java.nio.DirectByteBuffer類中Cleaner的應用,如下:
cleaner是該類的一個私有屬性,在構造函數中初始化,當DirectByteBuffer執行個體被垃圾回收器回收掉了,這個cleaner屬性對應的Cleaner執行個體就沒有其他強引用了,隻剩下Cleaner内部維護的連結清單對其的虛引用了,就會被垃圾回收器加入到Reference的pending隊列中,等待被處理。
二、ReferenceQueue
1、定義
ReferenceQueue主要用來通知Reference執行個體的使用方Reference執行個體對應的referent對象已經被回收掉了,允許使用方對Reference執行個體本身做适當的處理。注意ReferenceQueue本身并不直接持有Reference執行個體的引用,如果Reference執行個體本身變得不可達了,則無論Reference執行個體對應的referent對象被回收掉了,Reference執行個體都不會被添加到ReferenceQueue中。
ReferenceQueue包含的屬性如下:
- static ReferenceQueue<Object> NULL = new Null<>(); //如果Reference執行個體的queue等于NULL,則表示該執行個體已經從隊列中移除
- static ReferenceQueue<Object> ENQUEUED = new Null<>(); // //如果Reference執行個體的queue等于ENQUEUED,則表示該執行個體已經加入到隊列中
- Lock lock = new Lock(); //改寫隊列的鎖
- volatile Reference<? extends T> head = null; //Reference連結清單的頭部
- long queueLength = 0; //表示Reference連結清單的長度
上面的Null 和Lock都是ReferenceQueue的内部類,如下:
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
static private class Lock { };
ReferenceQueue跟正常的隊列實作不同,ReferenceQueue依賴于Reference的next屬性構成一個連結清單,連結清單頭就是ReferenceQueue的靜态head屬性,加入到隊列中實際就是插入到連結清單的頭部。當Reference執行個體加入到ReferenceQueue中,Reference執行個體變成新的連結清單頭,next屬性就指向原來的連結清單頭,queue屬性變成ENQUEUED,相關邏輯在enqueue方法中;當Reference執行個體從ReferenceQueue中移除時,next屬性被重置為自己,原來的next屬性變成新的連結清單頭,queue屬性變成NULL,相關邏輯在reallyPoll方法中。重點關注以下方法的實作。
2、 enqueue / reallyPoll
enqueue方法将Reference執行個體加入到連結清單的頭部,reallyPoll方法移除并傳回連結清單的頭部,這兩方法都要求擷取lock屬性的鎖,其中enqueue是public方法,reallyPoll方法是private方法。具體實作如下:
//enqueue方法将某個Reference執行個體加入到隊列中
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插入到連結清單的頭部,r的queue置為ENQUEUED,表示其已經加入到隊列中,r的next屬性置為它自己
r.queue = ENQUEUED;
//插入第一個元素時,head等于null,此時r的next屬性就是r,插入以後的元素時,next屬性就是head
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
//增加FinalReference的計數
sun.misc.VM.addFinalRefCount(1);
}
//喚醒其他的等待線程
lock.notifyAll();
return true;
}
}
private Reference<? extends T> reallyPoll() { /* Must hold lock */
Reference<? extends T> r = head;
if (r != null) {
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
//将連結清單頭從連結清單中移除,移除的Reference執行個體的queue會被置為NULL,next置為它自己
head = (rn == r) ? null : rn;
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
//FinalReference的計數器減1
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
3、poll / remove
這兩方法都是移除并傳回連結清單的頭元素,差別在于poll方法不會阻塞,立即傳回null,remove方法會阻塞目前線程,直到目前線程擷取了一個Reference執行個體或者累計等待時間超過了指定時間,remove方法還有一個沒有參數的重載版本,會阻塞目前線程,直到目前線程被喚醒,等待時間無限制,如果被喚醒了連結清單頭還是null則傳回null,即隻等待一次。其實作如下:
/**
* 從目前隊列中移除并傳回連結清單頭元素,如果為空則傳回null,WeakHashMap中就是調用此方法周遊隊列中所有的Reference執行個體
*/
public Reference<? extends T> poll() {
if (head == null)
//隊列為空,傳回null
return null;
synchronized (lock) {
//擷取隊列的鎖,移除并傳回連結清單頭元素
return reallyPoll();
}
}
/**
* 移除并傳回隊列的頭元素,最多等待timeout,如果timeout等于0,則隻等待一次直到線程被喚醒,等待時間無限制
*/
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException
{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPoll();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
//相當于while死循環
for (;;) {
//等待最多timeout,如果timeout為0,則等待其他線程調用notify或者notifyAll
lock.wait(timeout);
//移除并傳回連結清單頭部元素
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) {
//檢查是否等待逾時
long end = System.nanoTime();
//如果timeout為0,則start為0,timeout算出來的就是一個負值,會立即傳回null,即隻wait一次
//如果timeout不為0,則可能wait多次,直到多次wait的累計時間大于設定的值,傳回null
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
//繼續下一次wait
start = end;
}
}
}
}
/**
* remove(long timeout)的重載版本,timeout固定傳0
*/
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
4、forEach
forEach方法用于周遊連結清單中的所有的Reference執行個體,通常用于調試目的,要求調用方不能保持對Reference執行個體的引用,避免影響其正常銷毀,其實作如下:
/**
用來周遊隊列中所有Reference執行個體,通常用于調試目的,要求調用方不能保持對Reference執行個體的引用,避免影響其正常銷毀
*/
void forEach(Consumer<? super Reference<? extends T>> action) {
//注意執行forEach時不要求擷取鎖,是以讀取的元素可能已經從隊列中移除了
for (Reference<? extends T> r = head; r != null;) {
action.accept(r);
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
if (rn == r) {
if (r.queue == ENQUEUED) {
//說明已經周遊到隊列最後一個元素,将r置為null
r = null;
} else {
//r.queue等于NULL,說明r已經從隊列中移除了,需要從head開始重新周遊,以後r後面多個元素都可能被移除了,而且
//此時也無法擷取下一個周遊元素的引用
r = head;
}
} else {
// next in chain
r = rn;
}
}
}
三、Finalizer
1、定義
Finalizer繼承自FinalReference,FinalReference是一種特殊的引用類型,主要用來輔助實作Object finalization機制,其定義如下:
Finalizer定義的屬性如下:
- static ReferenceQueue<Object> queue = new ReferenceQueue<>(); //全局的ReferenceQueue隊列
- static Finalizer unfinalized = null; //所有Finalizer 執行個體構成的連結清單的頭元素
- static final Object lock = new Object(); //修改連結清單的鎖
- private Finalizer next = null, prev = null; //用來構成連結清單的表示下一個和上一個元素
Finalizer是借助垃圾回收器對Reference執行個體的特殊處理機制實作的,每建立一個實作了finalize方法的對象時,JVM會通過調用Finalizer的register方法建立一個新的Finalizer執行個體,該對象就是Finalizer執行個體的referent對象,所有的Finalizer執行個體構成一個連結清單。當某個對象隻被Finalizer執行個體所引用,則将對應的Finalizer執行個體加入到Reference維護的pending連結清單中,通過ReferenceHandler Thread将pending連結清單中的Finalizer執行個體加入到Finalizer定義的全局ReferenceQueue中。Finalizer自身會另外起一個新線程,FinalizerThread,不斷的從全局的ReferenceQueue中取出帶出來的Finalizer執行個體,然後将該執行個體從Finalizer連結清單中移除,最後調用對應對象的finalize方法執行資源的清理,并将對referent對象的引用置為null,保證該對象能夠會回收掉。當JVM程序即将退出,JVM會通過java.lang.Runtime另起線程處理掉全局ReferenceQueue中未處理完的Finalizer執行個體,通過java.lang.Shutdown另起線程處理掉Finalizer連結清單中的Finalizer執行個體,即沒有加入到Reference維護的pending連結清單中的Finalizer執行個體。重點關注以下方法的實作。
2、register
register是JVM建立對象時,如果該類實作了finalize方法,則以新建立的對象作為參數調用此方法建立一個Finalizer執行個體,并将其加入到Finalizer連結清單的頭部,其實作如下:
/*在對象建立的時候由JVM調用 */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
private Finalizer(Object finalizee) {
super(finalizee, queue);
//執行構造方法的時候,會将目前執行個體加入到連結清單中
add();
}
private void add() {
synchronized (lock) {
//擷取鎖,将this插入到連結清單的頭部
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
3、FinalizerThread
FinalizerThread就是一個不斷循環的線程任務,從queue屬性中擷取待處理的Finalizer執行個體,将該執行個體從Finalizer連結清單中移除然後調用其finalize方法,最後将Finalizer執行個體對referent對象的引用置為null,進而保證GC能夠正确回收該對象,其實作如下:
private static class FinalizerThread extends Thread {
//标記運作狀态
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
//已運作
if (running)
return;
//isBooted傳回false表示JVM未初始化完成
while (!VM.isBooted()) {
try {
//等待JVM初始化完成
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
//移除并傳回連結清單頭元素,如果為空則等待
Finalizer f = (Finalizer)queue.remove();
//執行Finalizer任務
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
//hasBeenFinalized傳回true,說明該元素已經從隊列移除了,直接傳回
if (hasBeenFinalized()) return;
//将目前執行個體從隊列中移除
remove();
}
try {
//擷取所引用的對象referent
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
//實際調用Object的finalize方法
jla.invokeFinalize(finalizee);
/* 去掉對finalizee的引用,讓GC回收掉該執行個體 */
finalizee = null;
}
} catch (Throwable x) { }
//将所引用的對象referent置為null,即去掉對referent的引用,讓GC回收掉該執行個體
super.clear();
}
private boolean hasBeenFinalized() {
//next等于this說明該執行個體已經從連結清單中移除了,已經執行過Finalized方法了
return (next == this);
}
private void remove() {
synchronized (lock) {
//擷取鎖,将this從連結清單中移除
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
//将next和prev都指向自己,表示已經從連結清單中移除
this.next = this; /* Indicates that this has been finalized */
this.prev = this;
}
}
FinalizerThread是通過靜态static塊啟動的,如下:
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();
}
上述jla.invokeFinalize(finalizee)方法的實作如下:
可以搜尋SharedSecrets.setJavaLangAccess方法的調用鍊找到上述實作,如下:
4、runFinalization / runAllFinalizers
runFinalization由Runtime.runFinalization()方法調用,負責清理掉queue中未處理的Finalizer執行個體;runAllFinalizers由java.lang.Shutdown,負責清理掉Finalizer連結清單中剩餘的即未加入到queue中的Finalizer執行個體;兩者都是調用forkSecondaryFinalizer方法執行清理任務,該方法會在系統線程組下另起一個線程執行指定任務,并等待該線程執行完成,如果執行異常,則終止目前線程。其實作如下:
/* Called by Runtime.runFinalization(),執行queue中未處理的Finalizer */
static void runFinalization() {
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
// in case of recursive call to run()
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
//不斷周遊queue中所有的Finalizer,然後執行finalize方法
Finalizer f = (Finalizer)queue.poll();
if (f == null) break;
f.runFinalizer(jla);
}
}
});
}
/* Invoked by java.lang.Shutdown,執行未加入到queue中的Finalizer */
static void runAllFinalizers() {
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
// in case of recursive call to run()
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
//不斷周遊unfinalized連結清單中的元素,執行finalize方法
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}}});
}
/*
在系統線程組下建立一個新的線程執行指定任務,并等待任務執行完成,之是以開啟一個新的線程,是為了與已經死鎖了或者停頓的finalizer thread隔離開來
加速finalize方法的執行
*/
private static void forkSecondaryFinalizer(final Runnable proc) {
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
//從目前線程的線程組往上周遊找到最初的系統線程組
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
//啟動一個新線程執行proc任務
Thread sft = new Thread(tg, proc, "Secondary finalizer");
sft.start();
try {
//等待proc任務執行完成
sft.join();
} catch (InterruptedException x) {
//執行異常,中斷目前線程
Thread.currentThread().interrupt();
}
return null;
}});
}
5、InstanceKlass::register_finalizer
該方法就是JVM中調用Finalizer的register方法的具體實作了,如下:
instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
if (TraceFinalizerRegistration) {
//列印跟蹤日志
tty->print("Registered ");
i->print_value_on(tty);
tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
}
//i是新建立的對象
instanceHandle h_i(THREAD, i);
//result表示調用結果
JavaValue result(T_VOID);
//args表示方法參數
JavaCallArguments args(h_i);
//擷取調用方法
methodHandle mh (THREAD, Universe::finalizer_register_method());
//執行方法調用
JavaCalls::call(&result, mh, &args, CHECK_NULL);
return h_i();
}
static Method* finalizer_register_method() { return _finalizer_register_cache->get_method(); }
_finalizer_register_cache的初始化在Universe的universe_post_init方法中,如下:
詳細代碼說明可以參考《Hotspot 記憶體管理之Universe 源碼解析》 。
register_finalizer方法的調用鍊如下:
InstanceKlass::allocate_instance就是根據Class建立對象的底層實作,可以參考《Hotspot Java對象建立和TLAB源碼解析》;另外幾個Runtime結尾的是給位元組碼的彙編指令或者編譯器的編譯代碼使用的方法,最終的調用場景一樣是建立對象。