欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java編程FinalReference與Finalizer原理示例詳解

 更新時(shí)間:2022年01月24日 10:13:08   作者:葉易_公眾號(hào)洞悉源碼  
這篇文章主要為大家介紹了java編程FinalReference與Finalizer的核心原理以及示例源碼的分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助

之前寫(xiě)了一篇java編程Reference核心原理示例源碼分析的文章,但由于篇幅和時(shí)間的原因沒(méi)有給出FinalReference和Finalizer的分析。同時(shí)也沒(méi)有說(shuō)明為什么建議不要重寫(xiě)Object#finalize方法(實(shí)際上JDK9已經(jīng)將Object#finalize方法標(biāo)記為Deprecated)。將文章轉(zhuǎn)發(fā)到perfma社區(qū)后,社區(qū)便有同學(xué)提出一個(gè)有意思的問(wèn)題?"Object#finalize如果在執(zhí)行的時(shí)候當(dāng)前對(duì)象又被重新賦值,那下次GC就不會(huì)再執(zhí)行finalize方法了,這是為什么啊” ??吹竭@個(gè)問(wèn)題時(shí)我知道答案一定和Finalizer有關(guān),于是便有了這篇幅文章。(ps:perfma社區(qū)有很多高質(zhì)量的文章,同時(shí)里面有很多實(shí)用的工具JVM參數(shù)分析、Java線程dump分析、Java內(nèi)存dump分析都有,感興趣的同學(xué)可以關(guān)注一下。)

概述

java編程Reference核心原理示例源碼分析一文中提到JDK中有SoftReference、WeakReference、PhantomReference以及FinalReference,但并沒(méi)有細(xì)說(shuō)FinalReference。最開(kāi)始Java語(yǔ)言其實(shí)就有了finalizers的機(jī)制,然后才引用了特殊Reference機(jī)制,也就是SoftReference、WeakReference、PhantomReference以及FinalReference,通過(guò)他們來(lái)處理資源或內(nèi)存回收的問(wèn)題。FinalReference與Finalizer平時(shí)開(kāi)發(fā)時(shí)是用不到,但你Debug、線程dump或者h(yuǎn)eap dump 分析時(shí),是否注意到Finalizer一直存在。

這個(gè)Finalizer到底是用來(lái)干什么的?為什么建議不要重寫(xiě)Object#finalize方法?為什么如果在執(zhí)行Object#finalize方法時(shí)當(dāng)前對(duì)象又被重新賦值,那下次GC就不會(huì)再執(zhí)行finalize方法了?本文將通過(guò)源碼分析解釋這些問(wèn)題。

初識(shí)FinalReference與Finalizer

JDK中FinalReference在JDK里的實(shí)現(xiàn)如下:

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

FinalReference實(shí)現(xiàn)很簡(jiǎn)單,可以說(shuō)就是一個(gè)標(biāo)記類,可以看到這個(gè)類訪問(wèn)權(quán)限為package,除了java.lang.ref包下面的類能引用其外其他類都無(wú)權(quán)限。Finalizer實(shí)現(xiàn)則相對(duì)復(fù)雜一點(diǎn)點(diǎn)。

final class Finalizer extends FinalReference<Object> {
    //存放Finalizer的引用隊(duì)列
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    //當(dāng)前等待待執(zhí)行Object#finalize方法的Finalizer節(jié)點(diǎn)
    private static Finalizer unfinalized = null;
    //鎖對(duì)象
    private static final Object lock = new Object();
    //Finalizer鏈 后續(xù)節(jié)點(diǎn)與前驅(qū)節(jié)點(diǎn)
    private Finalizer next = null, prev = null;
    //私有構(gòu)造函數(shù)
    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        //頭插法將當(dāng)前對(duì)象加入Finalizer鏈中
        add();
    }
    /* Invoked by VM */
    static void register(Object finalizee) {new Finalizer(finalizee);}
    //頭插法將當(dāng)前對(duì)象加入Finalizer鏈中
    private void add() {
        //獲取Finalizer類中全局鎖對(duì)象對(duì)應(yīng)moniter
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            //更新等待待執(zhí)行Object#finalize方法的節(jié)點(diǎn)
            unfinalized = this;
        }
    }
}

從上面的JDK源碼代碼可以看到Finalizer對(duì)象實(shí)際是JVM通過(guò)調(diào)用Finalizer#register方法創(chuàng)建的,不通過(guò)反射我們是無(wú)法直接創(chuàng)建Finalizer對(duì)象的。Finalizer#register方法一方面創(chuàng)建了Finalizer對(duì)象,同時(shí)將創(chuàng)建的Finalizer對(duì)象加入到了Finalizer鏈中。實(shí)際上HotSpot實(shí)現(xiàn)上在創(chuàng)建一對(duì)象時(shí),如果該類重寫(xiě)了Object#finalize方法且方法內(nèi)容不為空,則會(huì)調(diào)Finalizer#register方法。

何時(shí)會(huì)調(diào)用類中重寫(xiě)的finalize方法

先看回顧一下上篇文章中最重的Reference核心處理流程。通常JVM在GC時(shí)如果發(fā)現(xiàn)一個(gè)對(duì)象只有對(duì)應(yīng)的Reference引用就會(huì)將其對(duì)應(yīng)的Reference對(duì)象加入到對(duì)應(yīng)的pending-reference鏈中,同時(shí)會(huì)通知ReferenceHandler線程。ReferenceHandler線程收到通知后,如果對(duì)應(yīng)的Reference對(duì)象不是Cleaner的實(shí)例,則會(huì)其將加入到ReferenceQueue隊(duì)列中等待其他的線程去從ReferenceQueue中取出元素做進(jìn)一步的清理工作。

同樣Reference核心處理流程也適用于Finalizer(Finalizer的超類實(shí)際是Reference),而用于處理ReferenceQueue中Finalizer的線程是FinalizerThread。其是Finalizer內(nèi)部的一個(gè)私有類,并且是一個(gè)守護(hù)線程。

private static class FinalizerThread extends Thread {
    private volatile boolean running;
    FinalizerThread(ThreadGroup g) {
        //這個(gè)便是一面提到dump線程時(shí)會(huì)出現(xiàn)的Finalizer線程的名字
        super(g, "Finalizer");
    }
    public void run() {
        // 避免重復(fù)調(diào)用run方法
        if (running)
            return;
        // Finalizer線程先于System.initializeSystemClass被調(diào)用。等待直到JavaLangAccess可以訪問(wèn)
        while (!VM.isBooted()) {
            try {
                VM.awaitBooted();
            } catch (InterruptedException x) {
                // ignore and continue
            }
        }
        final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
        running = true;
        //守護(hù)線程一直運(yùn)行
        for (;;) {
            try {
                //從ReferenceQueue中取出Finalizer
                Finalizer f = (Finalizer)queue.remove();
                //調(diào)用Finalizer引用對(duì)象重寫(xiě)的finalize方法,內(nèi)部實(shí)現(xiàn)上會(huì)catch Throwable 異常,保證FinalizerThread線程一直能運(yùn)行
                f.runFinalizer(jla);
            } catch (InterruptedException x) {
                // ignore and continue
            }
        }
    }
}
static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());     
    Thread finalizer = new FinalizerThread(tg);
    //線程優(yōu)先級(jí)沒(méi)有ReferenceHandler守護(hù)線程高
    finalizer.setPriority(Thread.MAX_PRIORITY - 2);
    //設(shè)置為守護(hù)線程
    finalizer.setDaemon(true);
    //啟動(dòng)線程
    finalizer.start();
}

Finalizer#runFinalizer方法如下:

private void runFinalizer(JavaLangAccess jla) {
    synchronized (this) {
        //已從Finalizer鏈中摘除,則不再執(zhí)行Finalizer引用的對(duì)象的finalize方法
        if (hasBeenFinalized()) return;
        remove();
    }
    try {
        //獲取Finalizer引用的對(duì)象
        Object finalizee = this.get();
        if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
            /**JavaLangAccess實(shí)現(xiàn)內(nèi)部會(huì)調(diào)用Finalizer引用的對(duì)象的finalize方法
             * 實(shí)際是調(diào)用System#setJavaLangAccess方法實(shí)例化的JavaLangAccess對(duì)象
             */ 
            jla.invokeFinalize(finalizee);
            //清除棧中包含的該變量引用,以降低conservative GC 錯(cuò)誤的保留該對(duì)象的機(jī)會(huì)
            finalizee = null;
        }
    } catch (Throwable x) { }
    super.clear();
}

問(wèn)題答案

從上面Finalizer#runFinalizer方法源碼可以看出一旦一個(gè)對(duì)象已從Finalizer鏈中摘除,則不再執(zhí)行Finalizer引用的對(duì)象的finalize方法,即使在其finalize方法中再次強(qiáng)引用其本身。而另一個(gè)問(wèn)題"為什么建議不要重寫(xiě)Object#finalize方法",一旦重寫(xiě)了finalize方法就無(wú)法保證其一定會(huì)在某次GC前一定能執(zhí)行完,這樣引用的對(duì)象只能在下次或者是后面GC時(shí)才會(huì)回收,這可能會(huì)出現(xiàn)內(nèi)存泄露或是其它的GC問(wèn)題。關(guān)于finalize引發(fā)的GC問(wèn)題,感興趣的同學(xué)可以看一下美團(tuán)基礎(chǔ)構(gòu)架大佬寫(xiě)的 RPC采用短鏈接導(dǎo)致YoungGC耗時(shí)過(guò)長(zhǎng)的問(wèn)題分析與優(yōu)化一文:一次 Young GC 的優(yōu)化實(shí)踐(FinalReference 相關(guān))

總結(jié)

本文分析了Finalizer的源碼,并給出了"為什么如果在執(zhí)行Object#finalize方法時(shí)當(dāng)前對(duì)象又被重新賦值,那下次GC就不會(huì)再執(zhí)行finalize方法了?"的答案。希望對(duì)大家有所幫忙。文章不正確處還望指正,同時(shí)歡迎關(guān)注個(gè)人技術(shù)公眾號(hào) 洞悉源碼,后序源源不斷地給大家分享各類干貨。最后再拋出一下問(wèn)題給大家,JDK9中已將Object#finalize方法標(biāo)志為Deprecated,但如果我們要實(shí)現(xiàn)資源回收這種功能該如何實(shí)現(xiàn)呢?

相關(guān)文章

  • Groovy的規(guī)則腳本引擎實(shí)例解讀

    Groovy的規(guī)則腳本引擎實(shí)例解讀

    這篇文章主要介紹了Groovy的規(guī)則腳本引擎實(shí)例解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • java異步編程詳解

    java異步編程詳解

    這篇文章主要介紹了java異步編程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 利用Java實(shí)現(xiàn)網(wǎng)站聚合工具

    利用Java實(shí)現(xiàn)網(wǎng)站聚合工具

    互聯(lián)網(wǎng)上有數(shù)以萬(wàn)億計(jì)的網(wǎng)站,每個(gè)網(wǎng)站大都具有一定的功能。搜索引擎雖然對(duì)互聯(lián)網(wǎng)上的部分網(wǎng)站建立了索引,但是其作為一個(gè)大而全的搜索系統(tǒng),無(wú)法很好的定位到一些特殊的需求。因此本文將介紹一個(gè)用java實(shí)現(xiàn)的網(wǎng)站數(shù)據(jù)聚合工具,需要的可以參考一下
    2022-01-01
  • Java e.printStackTrace()案例講解

    Java e.printStackTrace()案例講解

    這篇文章主要介紹了Java e.printStackTrace()案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 關(guān)于java的九個(gè)預(yù)定義Class對(duì)象

    關(guān)于java的九個(gè)預(yù)定義Class對(duì)象

    這篇文章主要介紹了關(guān)于java的九個(gè)預(yù)定義Class對(duì)象,在Java中,沒(méi)有類就無(wú)法做任何事情。然而,并不是所有的類都具有面向?qū)ο筇卣?。如Math.random,并只需要知道方法名和參數(shù),需要的朋友可以參考下
    2023-05-05
  • spring?cloud?使用oauth2?問(wèn)題匯總

    spring?cloud?使用oauth2?問(wèn)題匯總

    這篇文章主要介紹了spring?cloud?使用oauth2?問(wèn)題匯總,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • java虛擬機(jī)原理:Class字節(jié)碼二進(jìn)制文件分析

    java虛擬機(jī)原理:Class字節(jié)碼二進(jìn)制文件分析

    class文件全名稱為Java class文件,主要在平臺(tái)無(wú)關(guān)性和網(wǎng)絡(luò)移動(dòng)性方面使Java更適合網(wǎng)絡(luò)。它在平臺(tái)無(wú)關(guān)性方面的任務(wù)是:為Java程序提供獨(dú)立于底層主機(jī)平臺(tái)的二進(jìn)制形式的服務(wù)。下面我們來(lái)詳細(xì)解讀下它吧
    2021-09-09
  • Java 判斷一個(gè)時(shí)間是否在另一個(gè)時(shí)間段內(nèi)

    Java 判斷一個(gè)時(shí)間是否在另一個(gè)時(shí)間段內(nèi)

    這篇文章主要介紹了Java 判斷一個(gè)時(shí)間是否在另一個(gè)時(shí)間段內(nèi)的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • Springboot集成Kafka實(shí)現(xiàn)producer和consumer的示例代碼

    Springboot集成Kafka實(shí)現(xiàn)producer和consumer的示例代碼

    這篇文章主要介紹了Springboot集成Kafka實(shí)現(xiàn)producer和consumer的示例代碼,詳細(xì)的介紹了什么是Kafka和安裝Kafka以及在springboot項(xiàng)目中集成kafka收發(fā)message,感興趣的小伙伴們可以參考一下
    2018-05-05
  • mall整合SpringSecurity及JWT認(rèn)證授權(quán)實(shí)戰(zhàn)下

    mall整合SpringSecurity及JWT認(rèn)證授權(quán)實(shí)戰(zhàn)下

    這篇文章主要為大家介紹了mall整合SpringSecurity及JWT認(rèn)證授權(quán)實(shí)戰(zhàn)第二篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論