面試:如何判斷一個對象是否存活?(或者GC對象的判定方法)?

這個問題,面試被問到的概率還是很大的。以下關於 如何判斷一個對象是否存活 的回答,完全參照《深入理解Java虛擬機》一書,有需要的可以看書學習。以下是題目解析

判斷對象是否存活的算法包括:

  • 引用計數算法
  • 可達性分析算法

問題的回答就是通過引用計數算法或者可達性分析算法去判斷一個對象是否存活,下面開始具體描述這兩種算法。

引用計數算法(Reference Counting)

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器加1;當引用失效時,計數器值減1;任何時刻計數器為0的對象就是不能再被引用的。

例如Object-C,Python語音使用引用計數算法進行內存管理。Java虛擬機沒有選用引用計數器算法來管理內存,其中最主要的原因是它很難解決對象之間相互循環引用的問題。

對象循環引用代碼示例:

public class ReferenceCountingGC {
 public Object instance = null;

 public static void testGC() {
 ReferenceCountingGC objA = new ReferenceCountingGC();
 ReferenceCountingGC objB = new ReferenceCountingGC();
 objA.instance = objB;
 objB.instance = objA;

 objA = null;
 objB = null;

 // 假設在這行發生GC, objA 和 objB是否能被回收?
 System.gc();
 }
}

對象objA和objB都有字段instance,賦值令 objA.instance = objB及objB.instance = objA,除此之外,這兩個對象再無任務引用,實際上這兩個對象已經不可能再被訪問,但是它們因為相互引用著對方,導致它們的引用計數都不為0,於是引用計數算法無法通知GC收集器回收它們。

面試:如何判斷一個對象是否存活?(或者GC對象的判定方法)?

可達性分析算法(Reachability Analysis)

可達性分析算法的基本思路是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則證明此對象是不可用的。

生存還是死亡

即使在可達性分析算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析後發現沒有與GC Roots相連接的引用,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”。
  
如果這個對象被判定為有必要執行finalize()方法,那麼這個對象將會放置在一個叫做F-Queue的隊列之中。並在稍後由一個虛擬機自動建立的,低優先級的Finalizer線程去執行它。這裡所謂“執行”是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣做的原因是,如果有一個對象在finalize()方法中執行緩慢,或者發生死循環,將可能會導致F-Queue隊列中其他對象永久處於等待,甚至導致整個內存回收系統崩潰。


  
finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模的標記,如果對象這個時候,未被重新引用,那它基本上就真的被回收了。

回收方法區

Java虛擬機規範中確實說過可以不要求虛擬機在方法區中實現垃圾回收,而且在方法區中進行垃圾回收的“性價比”一般比較低,方法區的垃圾收集主要回收兩部分內容:廢棄的常量和無用的類。

廢棄的常量,以常量池中字面量的回收為例,假如一個字符串“abc”已經進入常量池中,但是當前系統已經沒有任何一個String對象叫做“abc”的,也沒有任何其他地方引用這個字面量,這個“abc”常量就會被清理出常量池。

判斷一個無用的類需要同時滿足下面3個條件才能算是“無用的類”

  • 該類的所有實例都已經被回收
  • 加載該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class對象已經沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。



分享到:


相關文章: