原創申明:本文由公衆號【猿燈塔】原創,轉載請說明出處標註
今天呢!燈塔君跟你們講: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_flag
和RegisterFinalizersAtInit
進行判斷,若是類重寫了finalize
方法,且方法體不爲空,則調用register_finalizer
函數,繼續看register_finalizer
函數的實現:
其中Universe::finalizer_register_method()
緩存的是jdk
中java.lang.ref.Finalizer
類的register
方法,實現以下:
在jvm中經過JavaCalls::call
觸發register
方法,將新建的對象O
封裝成一個Finalizer
對象,並經過add
方法添加到Finalizer
鏈表頭。
對象O
和Finalizer
類的靜態變量unfinalized
有聯繫,在發生GC時,會被斷定爲活躍對象,所以不會被回收回收
在Finalizer
類的靜態代碼塊中會建立一個FinalizerThread
類型的守護線程,可是這個線程的優先級比較低,意味着在cpu吃緊的時候可能會搶佔不到資源執行。
FinalizerThread
線程負責從ReferenceQueue
隊列中獲取Finalizer
對象,若是隊列中
沒有元素,則經過wait
方法將該線程掛起,等待被喚醒
若是返回了Finalizer
對象,執行對象的runFinalizer()
方法,其實能夠發現:在runFinalizer()
方法中主動捕獲了異常,即便在執行finalize
方法拋出異常時,也沒有關係。
經過hasBeenFinalized
方法判斷該對象是否還在鏈表中,並將該Finalizer
對象從鏈表中刪除,這樣下次gc時就能夠把原對象給回收掉了,最後調用了native方法invokeFinalizeMethod
,其中invokeFinalizeMethod
方法最終會找到並執行對象的finalize
方法。
有個疑問:既然FinalizerThread
線程是從ReferenceQueue
隊列中獲取Finalizer
對象,那麼Finalizer對象是在什麼狀況下才會被插入到ReferenceQueue隊列中?
Finalizer
的祖父類Reference
中定義了ReferenceHandler
線程,實現以下:
當pending
被設置時,會調用ReferenceQueue
的enqueue
方法把Finalizer
對象插入到ReferenceQueue
隊列中,接着經過notifyAll
方法喚醒FinalizerThread
線程執行後續邏輯,實現以下:
pending字段何時會被設置?
在GC過程的引用處理階段,經過oopDesc::atomic_exchange_oop
方法把發現的引用列表設置在pending
字段所在的地址
日常使用的Socket通訊,SocksSocketImpl
的父類重寫了finalize
方法
這麼作主要是爲了確保在用戶忘記手動關閉socket
鏈接的狀況下,在該對象被回收時可以自動關閉socket
來釋放一些資源,可是在開發過程當中,真的忘記手動調用了close
方法,那麼這些socket
對象可能會由於FinalizeThread
線程遲遲沒有執行到這些對象的finalize
方法,而致使一直佔用某些資源,形成內存泄露。
365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回覆【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板