《以 ZGC 為例专缠,談一談 JVM 是如何實現(xiàn) Reference 語義的(上)》
《以 ZGC 為例涨缚,談一談 JVM 是如何實現(xiàn) Reference 語義的(中)》
7. FinalReference 如何使 GC 過程變得磨磨唧唧
FinalReference 對于我們來說是一種比較陌生的 Reference 類型悔政,因為我們好像在各大中間件以及 JDK 中并沒有見過它的應(yīng)用場景,事實上,F(xiàn)inalReference 被設(shè)計出來的目的也不是給我們用的,而是給 JVM 用的,它和 Java 對象的 finalize()
方法執(zhí)行機(jī)制有關(guān)祈餐。
public class Object {
@Deprecated(since="9")
protected void finalize() throws Throwable { }
}
我們看到 finalize()
方法在 OpenJDK9 中已經(jīng)被標(biāo)記為 @Deprecated
了阶祭,并不推薦使用。筆者其實一開始也并不想提及它懊烤,但是思來想去梯醒,本文是主要介紹各類 Refernce 語義實現(xiàn)的,前面筆者已經(jīng)非常詳細(xì)的介紹了 SoftReference腌紧,WeakReference茸习,PhantomReference 在 JVM 中的實現(xiàn)。
在文章的最后何不利用這個 FinalReference 將前面介紹的內(nèi)容再次為大家串聯(lián)一遍壁肋,加深一下大家對 Reference 整個處理鏈路的理解号胚,基于這個目的,才有了本小節(jié)的內(nèi)容浸遗。但筆者的本意并不是為了讓大家使用它猫胁。
下面我們還是按照老規(guī)矩,繼續(xù)從 JDK 以及 JVM 這兩個視角全方位的介紹一下 FinalReference 的實現(xiàn)機(jī)制跛锌,并為大家解釋一下這個 FinalReference 如何使整個 GC 過程變得拖拖拉拉弃秆,磨磨唧唧~~~
7.1 從 JDK 視角看 FinalReference
FinalReference 本質(zhì)上來說它也是一個 Reference,所以它的基本語義和 WeakReference 保持一致髓帽,JVM 在 GC 階段對它的整體處理流程和 WeakReference 也是大致一樣的菠赚。
唯一一點不同的是,由于 FinalReference 是和被它引用的 referent 對象的 finalize()
執(zhí)行有關(guān)郑藏,當(dāng)一個普通的 Java 對象在整個 JVM 堆中只有 FinalReference 引用它的時候锈至,按照 WeakReference 的基礎(chǔ)語義來講,這個 Java 對象就要被回收了译秦。
但是在這個 Java 對象被回收之前峡捡,JVM 需要保證它的 finalize()
被執(zhí)行到,所以 FinalReference 會再次將這個 Java 對象重新標(biāo)記為 alive筑悴,也就是在 GC 階段重新復(fù)活這個 Java 對象们拙。
后面的流程就和其他 Reference 一樣了,F(xiàn)inalReference 也會被 JVM 加入到 _reference_pending_list 鏈表中阁吝,ReferenceHandler 線程被喚醒砚婆,隨后將這個 FinalReference 從 _reference_pending_list 上摘下,并加入到與其關(guān)聯(lián)的 ReferenceQueue 中突勇,這個流程就是我們第三小節(jié)主要討論的內(nèi)容装盯,大家還記得嗎 ?
和 Cleaner 不同的是甲馋,對于 FinalReference 來說埂奈,在 JDK 中還有一個叫做 FinalizerThread
線程來專門處理它,FinalizerThread
線程會不斷的從與 FinalReference 關(guān)聯(lián)的 ReferenceQueue 中定躏,將所有需要被處理的 FinalReference 摘下账磺,然后挨個執(zhí)行被它所引用的 referent 對象的 finalize()
方法芹敌。
隨后在下一輪的 GC 中,F(xiàn)inalReference 對象以及它引用的 referent 對象才會被 GC 回收掉垮抗。
以上就是 FinalReference 被 JVM 處理的整個生命周期氏捞,下面讓我們先回到最初的起點,這個 FinalReference 是怎么和一個 Java 對象關(guān)聯(lián)起來的呢 冒版?
我們知道 FinalReference 是和 Java 對象的 finalize()
方法執(zhí)行有關(guān)的液茎,如果一個 Java 類沒有重寫 finalize()
方法,那么在創(chuàng)建這個 Java 類的實例對象的時候?qū)⒉粫瓦@個 FinalReference 有任何的瓜葛辞嗡,它就是一個普通的 Java 對象豁护。
但是如何一個 Java 類重寫了 finalize()
方法 ,那么在創(chuàng)建這個 Java 類的實例對象的時候欲间, JVM 就會將一個 FinalReference 實例和這個 Java 對象關(guān)聯(lián)起來楚里。
instanceOop InstanceKlass::allocate_instance(TRAPS) {
// 判斷這個類是否重寫了 finalize() 方法
bool has_finalizer_flag = has_finalizer();
instanceOop i;
// 創(chuàng)建實例對象
i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
// 如果該對象重寫了 finalize() 方法
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
// JVM 這里就會調(diào)用 Finalizer 類的靜態(tài)方法 register
// 將這個 Java 對象與 FinalReference 關(guān)聯(lián)起來
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
我們看到,在 JVM 創(chuàng)建對象實例的時候猎贴,會首先通過 has_finalizer()
方法判斷這個 Java 類有沒有重寫 finalize()
方法班缎,如果重寫了就會調(diào)用 register_finalizer
方法,JVM 最終會調(diào)用 JDK 中的 Finalizer 類的靜態(tài)方法 register她渴。
final class Finalizer extends FinalReference<Object> {
static void register(Object finalizee) {
new Finalizer(finalizee);
}
}
在這里 JVM 會將剛剛創(chuàng)建出來的普通 Java 對象 —— finalizee达址,與一個 Finalizer 對象關(guān)聯(lián)起來, Finalizer 對象的類型正是 FinalReference 趁耗。這里我們可以看到沉唠,當(dāng)一個 Java 類重寫了 finalize()
方法的時候,每當(dāng)創(chuàng)建一個該類的實例對象苛败,JVM 就會自動創(chuàng)建一個對應(yīng)的 Finalizer 對象满葛。
Finalizer 的整體設(shè)計和之前介紹的 Cleaner 非常相似,不同的是 Cleaner 是一個 PhantomReference罢屈,而 Finalizer 是一個 FinalReference嘀韧。
它們都有一個 ReferenceQueue,只不過 Cleaner 中的那個基本沒啥用缠捌,但是 Finalizer 中的這個 ReferenceQueue 卻有非常重要的作用锄贷。
它們內(nèi)部都有一個雙向鏈表,里面包含了 JVM 堆中所有的 Finalizer 對象曼月,用來確保這些 Finalizer 在執(zhí)行 finalizee 對象的 finalize()
方法之前不會被 GC 回收掉谊却。
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 雙向鏈表,保存 JVM 堆中所有的 Finalizer 對象哑芹,防止 Finalizer 被 GC 掉
private static Finalizer unfinalized = null;
private Finalizer next, prev;
private Finalizer(Object finalizee) {
super(finalizee, queue);
// push onto unfinalized
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
}
在創(chuàng)建 Finalizer 對象的時候炎辨,首先會調(diào)用父類方法,將被引用的 Java 對象以及 ReferenceQueue 關(guān)聯(lián)注冊到 FinalReference 中绩衷。
Reference(T referent, ReferenceQueue<? super T> queue) {
// 被引用的普通 Java 對象
this.referent = referent;
// Finalizer 中的 ReferenceQueue 實例(全局)
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
最后將這個 Finalizer 對象插入到雙向鏈表 —— unfinalized 中蹦魔。
這個結(jié)構(gòu)是不是和第三小節(jié)中我們介紹的 Cleaner 非常相似。
而 Cleaner 最后是被 ReferenceHandler 線程執(zhí)行的咳燕,那這個 Finalizer 最后是被哪個線程執(zhí)行的呢 勿决?
這里就要引入另一個 system thread 了,在 Finalizer 類初始化的時候會創(chuàng)建一個叫做 FinalizerThread 的線程招盲。
final class Finalizer extends FinalReference<Object> {
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
// 獲取 system thread group
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
// 創(chuàng)建 system thread : FinalizerThread
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
}
FinalizerThread 的優(yōu)先級被設(shè)置為 Thread.MAX_PRIORITY - 2
低缩,還記得 ReferenceHandler 線程的優(yōu)先級嗎 ?
public abstract class Reference<T> {
static {
Thread handler = new ReferenceHandler(tg, "Reference Handler");
// 設(shè)置 ReferenceHandler 線程的優(yōu)先級為最高優(yōu)先級
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
}
}
而一個普通的 Java 線程曹货,它的默認(rèn)優(yōu)先級是多少呢 咆繁?
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
我們可以看出這三類線程的調(diào)度優(yōu)先級為:ReferenceHandler > FinalizerThread > Java 業(yè)務(wù) Thead
。
FinalizerThread 線程在運(yùn)行起來之后顶籽,會不停的從一個 queue 中獲取 Finalizer 對象玩般,然后執(zhí)行 Finalizer 中的 runFinalizer 方法,這個邏輯是不是和 ReferenceHandler 線程不停的從 _reference_pending_list 中獲取 Cleaner 對象礼饱,然后執(zhí)行 Cleaner 的 clean 方法非常相似坏为。
private static class FinalizerThread extends Thread {
public void run() {
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
這個 queue 就是 Finalizer 中定義的 ReferenceQueue,在 JVM 創(chuàng)建 Finalizer 對象的時候镊绪,會將重寫了 finalize()
方法的 Java 對象與這個 ReferenceQueue 一起注冊到 FinalReference 中匀伏。
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private Finalizer(Object finalizee) {
super(finalizee, queue);
}
}
那這個 ReferenceQueue 中的 Finalizer 對象是從哪里添加進(jìn)來的呢 ?這就又和我們第三小節(jié)中介紹的內(nèi)容遙相呼應(yīng)起來了蝴韭,就是 ReferenceHandler 線程添加進(jìn)來的够颠。
private static class ReferenceHandler extends Thread {
private static void processPendingReferences() {
// ReferenceHandler 線程等待 JVM 向 _reference_pending_list 填充 Reference 對象
waitForReferencePendingList();
// 用于指向 JVM 的 _reference_pending_list
Reference<?> pendingList;
synchronized (processPendingLock) {
// 獲取 _reference_pending_list,隨后將 _reference_pending_list 置為 null
// 方便 JVM 在下一輪 GC 處理其他 Reference 對象
pendingList = getAndClearReferencePendingList();
}
// 將 pendingList 中的 Reference 對象挨個從鏈表中摘下處理
while (pendingList != null) {
// 從 pendingList 中摘下 Reference 對象
Reference<?> ref = pendingList;
pendingList = ref.discovered;
ref.discovered = null;
// 如果該 Reference 對象是 Cleaner 類型榄鉴,那么在這里就會調(diào)用它的 clean 方法
if (ref instanceof Cleaner) {
// Cleaner 的 clean 方法就是在這里調(diào)用的
((Cleaner)ref).clean();
} else {
// 這里處理除 Cleaner 之外的其他 Reference 對象
// 比如,其他 PhantomReference蹬耘,WeakReference减余,SoftReference,F(xiàn)inalReference
// 將他們添加到各自注冊的 ReferenceQueue 中
ref.enqueueFromPending();
}
}
}
}
當(dāng)一個 Java 對象在 JVM 堆中只有 Finalizer 對象引用如筛,除此之外沒有任何強(qiáng)引用或者軟引用之后抒抬,JVM 首先會將這個 Java 對象復(fù)活,在本次 GC 中并不會回收它妖胀,隨后會將這個 Finalizer 對象插入到 JVM 內(nèi)部的 _reference_pending_list 中,然后從 waitForReferencePendingList()
方法上喚醒 ReferenceHandler 線程赚抡。
ReferenceHandler 線程將 _reference_pending_list 中的 Reference 對象挨個摘下涂臣,注意 _reference_pending_list 中保存的既有 Cleaner,也有其他的 PhantomReference赁遗,WeakReference署辉,SoftReference,當(dāng)然也有本小節(jié)的 Finalizer 對象岩四。
如果摘下的是 Cleaner 對象那么就執(zhí)行它的 clean 方法哭尝,如果是其他 Reference 對象,比如這里的 Finalizer剖煌,那么就通過 ref.enqueueFromPending()
刚夺,將這個 Finalizer 對象插入到它的 ReferenceQueue 中。
當(dāng)這個 ReferenceQueue 有了 Finalizer 對象之后末捣,F(xiàn)inalizerThread 線程就會被喚醒侠姑,然后執(zhí)行 Finalizer 對象的 runFinalizer 方法。
Finalizer 的內(nèi)部有一個雙向鏈表 —— unfinalized箩做,它保存了當(dāng)前 JVM 堆中所有的 Finalizer 對象莽红,目的是為了避免在執(zhí)行其引用的 referent 對象的 finalize()
方法之前被 GC 掉。
在 runFinalizer 方法中首先要做的就是將這個 Finalizer 對象從雙向鏈表 unfinalized 上摘下邦邦,然后執(zhí)行 referent 對象的 finalize()
方法安吁。這里我們可以看到,大家在 Java 類中重寫的 finalize()
方法就是在這里被執(zhí)行的燃辖。
private void runFinalizer(JavaLangAccess jla) {
synchronized (lock) {
if (this.next == this) // already finalized
return;
// 將 Finalizer 對象從雙向鏈表 unfinalized 上摘下
if (unfinalized == this)
unfinalized = this.next;
else
this.prev.next = this.next;
if (this.next != null)
this.next.prev = this.prev;
this.prev = null;
this.next = this; // mark as finalized
}
try {
// 獲取 Finalizer 引用的 Java 對象
Object finalizee = this.get();
if (!(finalizee instanceof java.lang.Enum)) {
// 執(zhí)行 java 對象的 finalize() 方法
jla.invokeFinalize(finalizee);
}
} catch (Throwable x) { }
// 調(diào)用 FinalReference 的 clear 方法鬼店,將其引用的 referent 對象置為 null
// 下一輪 gc 的時候這個 FinalReference 以及它的 referent 對象就會被回收掉了。
super.clear();
}
最后調(diào)用 Finalizer 對象(FinalReference類型)的 clear 方法黔龟,將其引用的 referent 對象置為 null , 在下一輪 GC 的時候妇智, 這個 Finalizer 對象以及它的 referent 對象就會被 GC 掉。
7.2 從 JVM 視角看 FinalReference
現(xiàn)在我們已經(jīng)從 JVM 的外圍熟悉了 JDK 處理 FinalReference 的整個流程氏身,本小節(jié)蛋欣,筆者將繼續(xù)帶著大家深入到 JVM 的內(nèi)部到踏,看看在 GC 的時候窝稿,JVM 是如何處理 FinalReference 的菩彬。
在本文 5.1 小節(jié)中,筆者為大家介紹了 ZGC 在 Concurrent Mark 階段如何處理 Reference 的整個流程秉馏,只不過當(dāng)時我們偏重于 Reference 基礎(chǔ)語義的實現(xiàn),還未涉及到 FinalReference 的處理帆竹。
但我們在明白了 Reference 基礎(chǔ)語義的基礎(chǔ)之上,再來看 FinalReference 的語義實現(xiàn)就很簡單了秒紧,總體流程是一樣的,只不過在一些地方做了些特殊的處理叙淌。
在 ZGC 的 Concurrent Mark 階段凿菩,當(dāng) GC 線程遍歷標(biāo)記到一個 FinalReference 對象的時候,首先會通過 should_discover
方法來判斷是否應(yīng)該將這個 FinalReference 對象插入到 _discovered_list 中获黔。判斷邏輯如下:
bool ZReferenceProcessor::should_discover(oop reference, ReferenceType type) const {
// 獲取 referent 對象的地址視圖
volatile oop* const referent_addr = reference_referent_addr(reference);
// 調(diào)整 referent 對象的視圖為 remapped + mark0 也就是 weakgood 視圖
// 獲取 FinalReference 引用的 referent 對象
const oop referent = ZBarrier::weak_load_barrier_on_oop_field(referent_addr);
// 如果 Reference 的狀態(tài)就是 inactive堵未,那么這里將不會重復(fù)將 Reference 添加到 _discovered_list 重復(fù)處理
if (is_inactive(reference, referent, type)) {
return false;
}
// referent 還被強(qiáng)引用關(guān)聯(lián)渗蟹,那么 return false 也就是說不能被加入到 discover list 中
if (is_strongly_live(referent)) {
return false;
}
// referent 還被軟引用有效關(guān)聯(lián),那么 return false 也就是說不能被加入到 discover list 中
if (is_softly_live(reference, type)) {
return false;
}
return true;
}
首先獲取這個 FinalReference 對象所引用的 referent 對象世落,如果這個 referent 對象在 JVM 堆中已經(jīng)沒有任何強(qiáng)引用或者軟引用了屉佳,那么就會將 FinalReference 對象插入到 _discovered_list 中。
但是在插入之前還要通過 is_inactive
方法判斷一下這個 FinalReference 對象是否在上一輪 GC 中被處理過了体箕,
bool ZReferenceProcessor::is_inactive(oop reference, oop referent, ReferenceType type) const {
if (type == REF_FINAL) {
return reference_next(reference) != NULL;
} else {
return referent == NULL;
}
}
對于 FinalReference 來說干旁,inactive 的標(biāo)志是它的 next 字段不為空。
public abstract class Reference<T> {
volatile Reference next;
}
這里的 next 字段是干嘛的呢 换薄?比如說,這個 FinalReference 對象在上一輪的 GC 中已經(jīng)被處理過了冲泥,那么在發(fā)生本輪 GC 之前凡恍,ReferenceHandler 線程就已經(jīng)將這個 FinalReference 插入到一個 ReferenceQueue 中浮还,這個 ReferenceQueue 是哪來的呢 ?
正是上小節(jié)中我們介紹的洼冻,JVM 創(chuàng)建 Finalizer 對象的時候傳入的這個 queue驾荣。
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private Finalizer(Object finalizee) {
super(finalizee, queue);
}
}
而 ReferenceQueue 中的 FinalReference 對象就是通過它的 next 字段鏈接起來的播掷,當(dāng)一個 FinalReference 對象被 ReferenceHandler 線程插入到 ReferenceQueue 中之后垒酬,它的 next 字段就不為空了勘究,也就是說一個 FinalReference 對象一旦進(jìn)入 ReferenceQueue口糕,它的狀態(tài)就變?yōu)?inactive 了。
那么在下一輪的 GC 中如果一個 FinalReference 對象的狀態(tài)是 inactive超棺,表示它已經(jīng)被處理過了,那么就不在重復(fù)添加到 _discovered_list 中了适肠。
如果一個 FinalReference 對象之前沒有被處理過侯养,并且它引用的 referent 對象當(dāng)前也沒有任何強(qiáng)引用或者軟引用關(guān)聯(lián),那么是不是說明這個 referent 就該被回收了 辩稽?想想 FinalReference 的語義是什么 ? 是不是就是在 referent 對象被回收之前還要調(diào)用它的 finalize()
方法 。
所以為了保證 referent 對象的 finalize()
方法得到調(diào)用到千,JVM 就會在 discover
方法中將其復(fù)活。隨后會將 FinalReference 對象插入到 _discovered_list 中了赵,這樣在 GC 之后 ,F(xiàn)inalizerThread 就會調(diào)用 referent 對象的 finalize()
方法了苛茂,這里是不是和上一小節(jié)的內(nèi)容呼應(yīng)起來了妓羊。
void ZReferenceProcessor::discover(oop reference, ReferenceType type) {
// 復(fù)活 referent 對象
if (type == REF_FINAL) {
// 獲取 referent 地址視圖
volatile oop* const referent_addr = reference_referent_addr(reference);
// 如果是 FinalReference 那么就需要對 referent 進(jìn)行標(biāo)記,視圖改為 finalizable 表示只能通過 finalize 方法才能訪問到 referent 對象
// 因為 referent 后續(xù)需要通過 finalize 方法被訪問,所以這里需要對它進(jìn)行標(biāo)記株婴,不能回收
ZBarrier::mark_barrier_on_oop_field(referent_addr, true /* finalizable */);
}
// Add reference to discovered list
// 確保 reference 不在 _discovered_list 中,不能重復(fù)添加
assert(reference_discovered(reference) == NULL, "Already discovered");
oop* const list = _discovered_list.addr();
// 頭插法,reference->discovered = *list
reference_set_discovered(reference, *list);
// reference 變?yōu)?_discovered_list 的頭部
*list = reference;
}
那么 JVM 如何將一個被 FinalReference 引用的 referent 對象復(fù)活呢 粮彤?
uintptr_t ZBarrier::mark_barrier_on_finalizable_oop_slow_path(uintptr_t addr) {
// Mark,這里的 Finalizable = true
return mark<GCThread, Follow, Finalizable, Overflow>(addr);
}
template <bool gc_thread, bool follow, bool finalizable, bool publish>
uintptr_t ZBarrier::mark(uintptr_t addr) {
uintptr_t good_addr;
// Mark导坟,在 _livemap 標(biāo)記位圖中將 referent 對應(yīng)的 bit 位標(biāo)記為 1
if (should_mark_through<finalizable>(addr)) {
ZHeap::heap()->mark_object<gc_thread, follow, finalizable, publish>(good_addr);
}
if (finalizable) {
// 調(diào)整 referent 對象的視圖為 finalizable
return ZAddress::finalizable_good(good_addr);
}
return good_addr;
}
其實很簡單屿良,首先通過 ZPage::mark_object
將 referent 對應(yīng)在標(biāo)記位圖 _livemap 的 bit 位標(biāo)記為 1。其次調(diào)整 referent 對象的地址視圖為 finalizable乍迄,表示該對象在回收階段被 FinalReference 復(fù)活管引。
inline bool ZPage::mark_object(uintptr_t addr, bool finalizable, bool& inc_live) {
// Set mark bit士败, 獲取 referent 對象在標(biāo)記位圖的索引 index
const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
// 將 referent 對應(yīng)的 bit 位標(biāo)記為 1
return _livemap.set(index, finalizable, inc_live);
}
到現(xiàn)在 FinalReference 對象已經(jīng)被加入到 _discovered_list 中了闯两,referent 對象也被復(fù)活了饥臂,隨后在 ZGC 的 Concurrent Process Non-Strong References 階段酵熙,JVM 就會將 _discovered_list 中的所有 Reference 對象(包括這里的 FinalReference)統(tǒng)統(tǒng)轉(zhuǎn)移到 _reference_pending_list 中,并喚醒 ReferenceHandler 線程去處理譬猫。
隨后 ReferenceHandler 線程將 _reference_pending_list 中的 FinalReference 對象在添加到 Finalizer 中的 ReferenceQueue 中。隨即 FinalizerThread 線程就會被喚醒闸准,然后執(zhí)行 Finalizer 對象的 runFinalizer 方法钥顽,最終就會執(zhí)行到 referent 對象的 finalize() 方法。這是不是就和上一小節(jié)中的內(nèi)容串起來了斩狱。
當(dāng) referent 對象的 finalize() 方法被 FinalizerThread 執(zhí)行完之后,下一輪 GC 的這時候灌具,這個 referent 對象以及與它關(guān)聯(lián)的 FinalReference 對象就會一起被 GC 回收了珠十。
從整個 JVM 對于 FinalReference 的處理過程可以看出,只要我們在一個 Java 類中重寫了 finalize() 方法姿搜,那么當(dāng)這個 Java 類對應(yīng)的實例可以被回收的時候绍载,它的 finalize() 方法是一定會被調(diào)用的。
調(diào)用的時機(jī)取決于 FinalizerThread 線程什么時候被 OS 調(diào)度到,但是從另外一個側(cè)面也可以看出届良,由于 FinalReference 的影響,一個原本該被回收的對象眉撵,在 GC 的過程又會被 JVM 復(fù)活蟆肆。而只有當(dāng)這個對象的 finalize() 方法被調(diào)用之后,該對象以及與它關(guān)聯(lián)的 FinalReference 只能等到下一輪 GC 的時候才能被回收。
如果 finalize() 方法執(zhí)行的很久又或者是 FinalizerThread 沒有被 OS 調(diào)度到,這中間可能已經(jīng)發(fā)生好幾輪 GC 了缴允,那么在這幾輪 GC 中,F(xiàn)inalReference 和他的 referent 對象就一直不會被回收薄料,表現(xiàn)的現(xiàn)象就是 JVM 堆中存在大量的 Finalizer 對象敞贡。
8. PhantomReference 和 WeakReference 究竟有何不同
PhantomReference 和 WeakReference 如果僅僅從概念上來說其實很難區(qū)別出他們之間究竟有何不同,比如摄职, PhantomReference 是用來跟蹤對象是否被垃圾回收的誊役,如果對象被 GC ,那么其對應(yīng)的 PhantomReference 就會被加入到一個 ReferenceQueue 中谷市,這個 ReferenceQueue 是在創(chuàng)建 PhantomReference 對象的時候注冊進(jìn)去的蛔垢。
我們在應(yīng)用程序中可以通過檢查這個 ReferenceQueue 中的 PhantomReference 對象,從而可以判斷出其引用的 referent 對象已經(jīng)被回收迫悠,隨即可以做一些釋放資源的工作鹏漆。
public class PhantomReference<T> extends Reference<T> {
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
而 WeakReference 的概念是,如果一個對象在 JVM 堆中已經(jīng)沒有任何強(qiáng)引用鏈或者軟引用鏈了及皂,在只有一個 WeakReference 引用它的情況下甫男,那么這個對象就會被 GC,與其對應(yīng)的 WeakReference 也會被加入到其注冊的 ReferenceQueue 中验烧。后面的套路和 PhantomReference 一模一樣板驳。
既然兩者在概念上都差不多,JVM 處理的過程也差不多碍拆,那么 PhantomReference 可以用來跟蹤對象是否被垃圾回收若治,WeakReference 可不可以跟蹤呢 ?
事實上感混,在大部分情況下 WeakReference 也是可以的端幼,但是在一種特殊的情況下 WeakReference 就不可以了,只能由 PhantomReference 來跟蹤對象的回收狀態(tài)弧满。
上圖中婆跑,object1 對象在 JVM 堆中被一個 WeakReference 對象和 FinalReference 對象同時引用,除此之外沒有任何強(qiáng)引用鏈和軟引用鏈庭呜,根據(jù) FinalReference 的語義滑进,這個 object1 是不是就要被回收了犀忱,但為了執(zhí)行它的 finalize() 方法所以 JVM 會將 object1 復(fù)活。
根據(jù) WeakReference 的語義扶关,此時發(fā)生了 GC阴汇,并且 object1 沒有任何強(qiáng)引用鏈和軟引用鏈,那么此時 JVM 是不是就會將 WeakReference 加入到 _reference_pending_list 中节槐,后面再由 ReferenceHandler 線程轉(zhuǎn)移到 ReferenceQueue 中搀庶,等待應(yīng)用程序的處理。
也就是說在這種情況下铜异,F(xiàn)inalReference 和 WeakReference 在本輪 GC 中哥倔,都會被 JVM 處理,但是 object1 卻是存活狀態(tài)熙掺,所以 WeakReference 不能跟蹤對象的垃圾回收狀態(tài)未斑。
object2 對象在 JVM 堆中被一個 PhantomReference 對象和 FinalReference 對象同時引用,除此之外沒有任何強(qiáng)引用鏈和軟引用鏈币绩,根據(jù) FinalReference 的語義蜡秽, JVM 會將 object2 復(fù)活。
但根據(jù) PhantomReference 的語義缆镣,只有在 object2 要被垃圾回收的時候芽突,JVM 才會將 PhantomReference 加入到 _reference_pending_list 中,但是此時 object2 已經(jīng)復(fù)活了董瞻,所以 PhantomReference 這里就不會被加入到 _reference_pending_list 中了寞蚌。
也就是說在這種情況下,只有 FinalReference 在本輪 GC 中才會被 JVM 處理钠糊,隨后 FinalizerThread 會調(diào)用 Finalizer 對象(FinalReference類型)的 runFinalizer 方法挟秤,最終就會執(zhí)行到 object2 對象的 finalize() 方法。
當(dāng) object2 對象的 finalize() 方法被執(zhí)行完之后抄伍,在下一輪 GC 中就會回收 object2 對象艘刚,那么根據(jù) PhantomReference 的語義,PhantomReference 對象只有在下一輪 GC 中才會被 JVM 加入到 _reference_pending_list 中截珍,隨后被 ReferenceHandler 線程處理攀甚。
所以在這種特殊的情況就只有 PhantomReference 才能用于跟蹤對象的垃圾回收狀態(tài),而 WeakReference 卻不可以岗喉。
那 JVM 是如何實現(xiàn) PhantomReference 和 WeakReference 的這兩種語義的呢 秋度?
首先在 ZGC 的 Concurrent Mark 階段,GC 線程會將 JVM 堆中所有需要被處理的 Reference 對象加入到一個臨時的 _discovered_list 中钱床。
隨后在 Concurrent Process Non-Strong References 階段荚斯,GC 會通過 should_drop
方法再次判斷 _discovered_list 中存放的這些臨時 Reference 對象所引用的 referent 是否存活 ?
如果這些 referent 仍然存活,那么就需要將對應(yīng)的 Reference 對象從 _discovered_list 中移除鲸拥。
如果這些 referent 不再存活拐格,那么就將對應(yīng)的 Reference 對象繼續(xù)保留在 _discovered_list僧免,最后將 _discovered_list 中的 Reference 對象全部轉(zhuǎn)移到 _reference_pending_list 中刑赶,隨后喚醒 ReferenceHandler 線程去處理。
PhantomReference 和 WeakReference 的核心區(qū)別就在這個 should_drop
方法中:
bool ZReferenceProcessor::should_drop(oop reference, ReferenceType type) const {
// 獲取 Reference 所引用的 referent
const oop referent = reference_referent(reference);
// 如果 referent 仍然存活懂衩,那么就會將 Reference 對象移除撞叨,不需要被 ReferenceHandler 線程處理
if (type == REF_PHANTOM) {
// 針對 PhantomReference 對象的特殊處理
return ZBarrier::is_alive_barrier_on_phantom_oop(referent);
} else {
// 針對 WeakReference 對象的處理
return ZBarrier::is_alive_barrier_on_weak_oop(referent);
}
}
should_drop 方法主要是用來判斷一個被 Reference 引用的 referent 對象是否存活,但是根據(jù) Reference 類型的不同浊洞,比如這里的 PhantomReference 和 WeakReference牵敷,具體的判斷邏輯是不一樣的。
根據(jù)前面幾個小節(jié)的內(nèi)容法希,我們知道 ZGC 是通過一個 _livemap 標(biāo)記位圖枷餐,來標(biāo)記一個對象的存活狀態(tài)的,ZGC 會將整個 JVM 堆劃分成一個一個的 page苫亦,然后從 page 中一個一個的分配對象毛肋。每一個 page 結(jié)構(gòu)中有一個 _livemap,用來標(biāo)記該 page 中所有對象的存活狀態(tài)屋剑。
class ZPage : public CHeapObj<mtGC> {
private:
ZLiveMap _livemap;
}
在 ZGC 中 ZPage 共分為三種類型:
// Page types
const uint8_t ZPageTypeSmall = 0;
const uint8_t ZPageTypeMedium = 1;
const uint8_t ZPageTypeLarge = 2;
ZPageTypeSmall 尺寸為 2M 润匙, SmallZPage 中的對象尺寸按照 8 字節(jié)對齊,最大允許的對象尺寸為 256K唉匾。
ZPageTypeMedium 尺寸和 MaxHeapSize 有關(guān)孕讳,一般會設(shè)置為 32 M,MediumZPage 中的對象尺寸按照 4K 對齊巍膘,最大允許的對象尺寸為 4M厂财。
ZPageTypeLarge 尺寸不定,但需要按照 2M 對齊峡懈。如果一個對象的尺寸超過 4M 就需要在 LargeZPage 中分配璃饱。
uintptr_t ZObjectAllocator::alloc_object(size_t size, ZAllocationFlags flags) {
if (size <= ZObjectSizeLimitSmall) {
// 對象 size 小于等于 256K ,在 SmallZPage 中分配
return alloc_small_object(size, flags);
} else if (size <= ZObjectSizeLimitMedium) {
// 對象 size 大于 256K 但小于等于 4M 逮诲,在 MediumZPage 中分配
return alloc_medium_object(size, flags);
} else {
// 對象 size 超過 4M 帜平,在 LargeZPage 中分配
return alloc_large_object(size, flags);
}
}
那么 ZPage 中的這個 _livemap 中的 bit 位個數(shù),是不是就應(yīng)該和一個 ZPage 所能容納的最大對象個數(shù)保持一致梅鹦,因為一個對象是否存活按理說是不是用一個 bit 就可以表示了 裆甩?
ZPageTypeSmall 中最大能容納的對象個數(shù)為
2M / 8B = 262144
,那么對應(yīng)的 _livemap 中是不是只要 262144 個 bit 就可以了齐唆。ZPageTypeMedium 中最大能容納的對象個數(shù)為
32M / 4K = 8192
嗤栓,那么對應(yīng)的 _livemap 中是不是只要 8192 個 bit 就可以了。ZPageTypeLarge 只會容納一個大對象。在 ZGC 中超過 4M 的就是大對象茉帅。
inline uint32_t ZPage::object_max_count() const {
switch (type()) {
case ZPageTypeLarge:
// A large page can only contain a single
// object aligned to the start of the page.
return 1;
default:
return (uint32_t)(size() >> object_alignment_shift());
}
}
但實際上 ZGC 中的 _livemap 所包含的 bit 個數(shù)是在此基礎(chǔ)上再乘以 2叨叙,也就是說一個對象需要用兩個 bit 位來標(biāo)記。
static size_t bitmap_size(uint32_t size, size_t nsegments) {
return MAX2<size_t>(size, nsegments) * 2;
}
那 ZGC 為什么要用兩個 bit 來標(biāo)記對象的存活狀態(tài)呢 堪澎?答案就是為了區(qū)分本小節(jié)中介紹的這種特殊情況擂错,一個對象是否存活分為兩種情況:
對象被 FinalReference 復(fù)活,這樣 ZGC 會標(biāo)記第一個低位 bit ——
1
樱蛤。對象存在強(qiáng)引用鏈钮呀,人家原本就應(yīng)該存活,這樣 ZGC 會將兩個 bit 位全部標(biāo)記 ——
11
昨凡。
而在本小節(jié)中我們討論的就是對象在被 FinalReference 復(fù)活的情況下爽醋,PhantomReference 和 WeakReference 的處理有何不同,了解了這些背景知識之后便脊,那么我們再回頭來看 should_drop 方法的判斷邏輯:
首先對于 PhantomReference 來說蚂四,在 ZGC 的 Concurrent Process Non-Strong References 階段是通過 ZBarrier::is_alive_barrier_on_phantom_oop
來判斷其引用的 referent 對象是否存活的每庆。
inline bool ZHeap::is_object_live(uintptr_t addr) const {
ZPage* page = _page_table.get(addr);
// PhantomReference 判斷的是第一個低位 bit 是否被標(biāo)記
// 而 FinalReference 復(fù)活 referent 對象標(biāo)記的也是第一個 bit 位
return page->is_object_live(addr);
}
inline bool ZPage::is_object_marked(uintptr_t addr) const {
// 獲取第一個 bit 位 index
const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
// 查看是否被 FinalReference 標(biāo)記過
return _livemap.get(index);
}
我們看到 PhantomReference 判斷的是第一個 bit 位是否被標(biāo)記過谱醇,而在 FinalReference 復(fù)活 referent 對象的時候標(biāo)記的就是第一個 bit 位。所以 should_drop 方法返回 true湾宙,PhantomReference 從 _discovered_list 中移除妒御。
而對于 WeakReference 來說解愤,卻是通過 Barrier::is_alive_barrier_on_weak_oop
來判斷其引用的 referent 對象是否存活的。
inline bool ZHeap::is_object_strongly_live(uintptr_t addr) const {
ZPage* page = _page_table.get(addr);
// WeakReference 判斷的是第二個高位 bit 是否被標(biāo)記
return page->is_object_strongly_live(addr);
}
inline bool ZPage::is_object_strongly_marked(uintptr_t addr) const {
const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
// 獲取第二個 bit 位 index
return _livemap.get(index + 1);
}
我們看到 WeakReference 判斷的是第二個高位 bit 是否被標(biāo)記過乎莉,所以這種情況下送讲,無論 referent 對象是否被 FinalReference 復(fù)活,should_drop 方法都會返回 false 惋啃。WeakReference 仍然會保留在 _discovered_list 中哼鬓,隨后和 FinalReference 一起被 ReferenceHandler 線程處理。
所以總結(jié)一下他們的核心區(qū)別就是:
PhantomReference 對象只有在對象被回收的時候边灭,才會被 ReferenceHandler 線程處理异希,它會被 FinalReference 影響。
WeakReference 對象只要是發(fā)生 GC , 并且它引用的 referent 對象沒有任何強(qiáng)引用鏈或者軟引用鏈的時候绒瘦,都會被 ReferenceHandler 線程處理称簿,不會被 FinalReference 影響。
總結(jié)
本文我們首先從中間件的角度惰帽,介紹了 SoftReference憨降,WeakReference,PhantomReference该酗,F(xiàn)inalReference 這些 Java 中定義的 Reference 的相關(guān)概念及其應(yīng)用場景授药。
后面我們從中間件的視角轉(zhuǎn)入到 JDK 中士嚎,介紹了 Cleaner,F(xiàn)inalizer悔叽,ReferenceHandler 線程莱衩,F(xiàn)inalizerThread 線程,ReferenceQueue 等在 JDK 層面處理 Reference 對象的重要設(shè)計娇澎。
最后我們又從 JDK 的視角轉(zhuǎn)入到 JVM 內(nèi)部笨蚁,詳細(xì)的介紹了 SoftReference,WeakReference九火,PhantomReference赚窃,F(xiàn)inalReference 在 JVM 中的實現(xiàn),通過分析 JVM 的源碼岔激,我們清楚了 SoftReference 的準(zhǔn)確回收時機(jī),F(xiàn)inalReference 如何拖慢整個 GC 過程是掰,以及 PhantomReference 與 WeakReference 的根本區(qū)別在哪里虑鼎。
在看完本文的全部內(nèi)容之后,筆者在第二小節(jié)中準(zhǔn)備的那六個問題键痛,大家現(xiàn)在可以回答了嗎 炫彩?