天天看點

Java垃圾回收機制-對象死亡判斷

作者:我是小向同學

在深入了解java虛拟機中,有這麼一段話,Java與c++之間有一堵由記憶體動态配置設定和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裡面的人卻想出來。

是以對于Java虛拟機來說,垃圾回收需要完成的三件事情:

  1. 那些記憶體需要回收?
  2. 什麼時候回收?
  3. 如何回收?

今天我們讨論第一個問題,哪些記憶體需要回收?Java coder都清楚在堆中存放着Java世界中幾乎所有的對象執行個體,垃圾收集器在對堆進行回收前,第一件事情就是要确定這些對象之中哪些還“存活着”,哪些已經“死去”(“死去”即不可能再被任何途徑使用的對象)了

1 引用計算法

即在對象中添加一個引用電腦,每當有一個地方引用它的時候,計數器值就加一;當引用失效時,電腦值就減一;任何時刻計數器為零的對象就是不可能再被使用的。

客觀來說,引用計數法(Reference Counting)雖然占用了一下額外的記憶體空間來進行計數,但它的原理簡單,判定效率也很高,在大多數情況下它都是一個不錯的算法,但是在javal領域,至少主流的java虛拟機裡面都沒有選用引用計數法來管理記憶體,主要原因就是這個看似簡單的算法有很多例外情況要考慮,必須要配合大量額外處理才能保證正确地工作,譬如單純的引用計數法就很難解決對象之間互相循環引用的問題。下面舉個簡單例子來進行說明

代碼清單 引用計數算法的缺陷
/**
 * testGC()方法執行後,objA和objB會不會被GC呢?
 * @author zzm
 */
public class ReferenceCountingGC {
 
    public Object instance = null;
 
    private static final int _1MB = 1024 * 1024;
 
    /**
     * 這個成員屬性的唯一意義就是占點記憶體,以便能在GC日志中看清楚是否有回收過
     */
    private byte[] bigSize = new byte[2 * _1MB];
 
    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();
    }
}           

運作結果表明虛拟機并沒有因為這兩個對象互相引用就放棄回收它們,這也從側面說明了Java虛拟機并不是通過引用計數算法來判斷對象是否存活的。(具體虛拟機如何表明的,感興趣的可以自己使用jdk自帶指令,進行驗證)。

2 可達性分析算法

目前主流的商用程式語言(Java,C#等)記憶體管理系統,都是通過可達性分析(Reachability Analysis)算法來判定對象是否存活的。這個算法的基本思路就是通過一系列稱為“GC Roots”的根對象作為起始點集,從這些節點開始,根據引用關系向下搜尋,搜尋過程所走過的路徑稱為“引用鍊”(Reference Chain),如果某個對象不可達時,則證明此對象是不可能再被使用的。

如下圖, 對象object5,object6,object7雖然互有關聯,但是它們到GC Roots是不可達的,是以它們将會被判定為可回收的對象。

Java垃圾回收機制-對象死亡判斷

在Java技術體系裡面,固定可作為GC Roots的對象包括以下幾種:

  • 在虛拟機棧(棧幀中的本地變量表)中引用的對象,譬如目前正在運作的方法所使用的參數,局部變量,臨時變量等。
  • 在方法區中類靜态屬性引用的對象,譬如Java類的引用類型靜态變量。
  • 在方法區中常量引用的對象,譬如字元串常量池(String Table)裡的引用。
  • 在本地方法棧中JNI(即平常是的Native方法)引用的對象。
  • Java虛拟機内部的引用,如基本資料類型對應的Class對象,以下常駐的異常對象(譬如NullPointException,OutOfMemortError)等,還有系統類加載器等
  • 所有被同步鎖(Synchronized關鍵字)持有的對象。
  • 反映Java虛拟機内部情況的JMXBean,JVMTI中注冊的回調,本地代碼緩存等。

當然除了這些固定的GC Roots集合以外,還可以根據使用者選擇的垃圾回收器以及目前回收記憶體區域不同,還可以有其他對象“臨時性”加入。

繼續閱讀