在Java語言中,可作為GC Roots的對象包含以下幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象。(可以理解為:引用棧幀中的本地變量表的所有對象)
- 方法區中靜態屬性引用的對象(可以理解為:引用方法區該靜態屬性的所有對象)
- 方法區中常量引用的對象(可以理解為:引用方法區中常量的所有對象)
- 本地方法棧中(Native方法)引用的對象(可以理解為:引用Native方法的所有對象)
可以理解為:
(1)首先第一種是虛擬機棧中的引用的對象,我們在程序中正常創建一個對象,對象會在堆上開辟一塊空間,同時會將這塊空間的地址作為引用保存到虛擬機棧中,如果對象生命周期結束了,那么引用就會從虛擬機棧中出棧,因此如果在虛擬機棧中有引用,就說明這個對象還是有用的,這種情況是最常見的。
(2)第二種是我們在類中定義了全局的靜態的對象,也就是使用了static關鍵字,由於虛擬機棧是線程私有的,所以這種對象的引用會保存在共有的方法區中,顯然將方法區中的靜態引用作為GC Roots是必須的。
(3)第三種便是常量引用,就是使用了static final關鍵字,由於這種引用初始化之后不會修改,所以方法區常量池里的引用的對象也應該作為GC Roots。最后一種是在使用JNI技術時,有時候單純的Java代碼並不能滿足我們的需求,我們可能需要在Java中調用C或C++的代碼,因此會使用native方法,JVM內存中專門有一塊本地方法棧,用來保存這些對象的引用,所以本地方法棧中引用的對象也會被作為GC Roots。
JVM之判斷對象是否存活(引用計數算法、可達性分析算法,最終判定)
finalize()方法最終判定對象是否存活:
即使在可達性分析算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷再次標記過程。
標記的前提是對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈。
1).第一次標記並進行一次篩選。
篩選的條件是此對象是否有必要執行finalize()方法。
當對象沒有覆蓋finalize方法,或者finzlize方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”,對象被回收。
2).第二次標記
如果這個對象被判定為有必要執行finalize()方法,那么這個對象將會被放置在一個名為:F-Queue的隊列之中,並在稍后由一條虛擬機自動建立的、低優先級的Finalizer線程去執行。這里所謂的“執行”是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束。這樣做的原因是,如果一個對象finalize()方法中執行緩慢,或者發生死循環(更極端的情況),將很可能會導致F-Queue隊列中的其他對象永久處於等待狀態,甚至導致整個內存回收系統崩潰。
Finalize()方法是對象脫逃死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規模標記,如果對象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個對象建立關聯即可,譬如把自己賦值給某個類變量或對象的成員變量,那在第二次標記時它將移除出“即將回收”的集合。如果對象這時候還沒逃脫,那基本上它就真的被回收了。
流程圖如下: