JVM源碼分析之深刻分析Object類finalize()方法的實現原理

原創申明:本文由公衆號【猿燈塔】原創,轉載請說明出處標註

今天呢!燈塔君跟你們講:java

深刻分析Object類finalize()方法的實現原理面試

若是類中重寫了finalize方法,當該類對象被回收時,finalize方法有可能會被觸發,下面經過一個例子說明finalize方法對垃圾回收有什麼影響。數組

`public class FinalizeCase {緩存

private static Block holder = null;微信

public static void main(String[] args) throws Exception {jvm

holder = new Block();socket

holder = null;ide

System.gc();函數

//System.in.read();oop

}

static class Block {

byte[] _200M = new byte[200*1024*1024];

}

}`

Block類中聲明一個佔用內存200M的數組,是爲了方便看出來gc以後是否回收了Block對象,執行完的gc日誌以下:

從gc日誌中能夠看出來,執行完System.gc()以後,Block對象被如期的回收了,若是在Block類中重寫了finalize方法,會是同樣的結果麼?

`static class Block {

byte[] _200M = new byte[200*1024*1024];

@Override

protected void finalize() throws Throwable {

System.out.println("invoke finalize");

}

}`

執行完成gc日誌以下:

和以前的gc日誌進行比較,發現finalize方法確實被觸發了,可是Block對象還在內存中,並無被回收,這是爲何?

下面對finalize方法的實現原理進行分析。

finalize實現原理

對象的初始化過程會對has_finalizer_flagRegisterFinalizersAtInit進行判斷,若是類重寫了finalize方法,且方法體不爲空,則調用register_finalizer函數,繼續看register_finalizer函數的實現:

其中Universe::finalizer_register_method()緩存的是jdkjava.lang.ref.Finalizer類的register方法,實現以下:

在jvm中經過JavaCalls::call觸發register方法,將新建的對象O封裝成一個Finalizer對象,並經過add方法添加到Finalizer鏈表頭。

對象OFinalizer類的靜態變量unfinalized有聯繫,在發生GC時,會被斷定爲活躍對象,所以不會被回收回收

FinalizerThread線程

Finalizer類的靜態代碼塊中會建立一個FinalizerThread類型的守護線程,可是這個線程的優先級比較低,意味着在cpu吃緊的時候可能會搶佔不到資源執行。

FinalizerThread線程負責從ReferenceQueue隊列中獲取Finalizer對象,若是隊列中

沒有元素,則經過wait方法將該線程掛起,等待被喚醒

若是返回了Finalizer對象,執行對象的runFinalizer()方法,其實能夠發現:在runFinalizer()方法中主動捕獲了異常,即便在執行finalize方法拋出異常時,也沒有關係。

經過hasBeenFinalized方法判斷該對象是否還在鏈表中,並將該Finalizer對象從鏈表中刪除,這樣下次gc時就能夠把原對象給回收掉了,最後調用了native方法invokeFinalizeMethod,其中invokeFinalizeMethod方法最終會找到並執行對象的finalize方法。

ReferenceHandler線程

有個疑問:既然FinalizerThread線程是從ReferenceQueue隊列中獲取Finalizer對象,那麼Finalizer對象是在什麼狀況下才會被插入到ReferenceQueue隊列中?

Finalizer

的祖父類Reference中定義了ReferenceHandler線程,實現以下:

pending被設置時,會調用ReferenceQueueenqueue方法把Finalizer對象插入到ReferenceQueue隊列中,接着經過notifyAll方法喚醒FinalizerThread線程執行後續邏輯,實現以下:

pending字段何時會被設置?

在GC過程的引用處理階段,經過oopDesc::atomic_exchange_oop方法把發現的引用列表設置在pending字段所在的地址

Finalizer致使的內存泄漏

日常使用的Socket通訊,SocksSocketImpl的父類重寫了finalize方法

這麼作主要是爲了確保在用戶忘記手動關閉socket鏈接的狀況下,在該對象被回收時可以自動關閉socket來釋放一些資源,可是在開發過程當中,真的忘記手動調用了close方法,那麼這些socket對象可能會由於FinalizeThread線程遲遲沒有執行到這些對象的finalize方法,而致使一直佔用某些資源,形成內存泄露。

365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回覆【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板
相關文章
相關標籤/搜索