垃圾回收器——如何判斷物件是否存活
垃圾回收器在對堆進行回收前,第一件事情就是要確定這些物件之中哪些還“存活”著,哪些已經“死去”[即已經不可能再被使用的物件]
引用計數演算法
給物件新增一個引用計數器,每當有一個地方引用它時,計數器的值就+1;當引用失效時,值就-1;任何時刻技術器為0的物件就是不能再被使用的。
引用計數器演算法的實現非常簡單,判定效率也很高,在大部分情況下它都是一個不錯的演算法,也有一些比較著名的應用案例,例如微軟公司的COM技術。但是,至少主流Java虛擬機器裡面沒有選用引用計數器演算法來管理記憶體,其中最重要的原因是它很難解決物件之間相互迴圈引用的問題
程式碼
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
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();
}
}
如果是引用計數器演算法,雖然這裡的兩個物件都已經不能被訪問,但是因為他們互相引用著對方,導致他們的引用計數都不為0,於是引用計數演算法無法通知GC收集器回收他們
可達性分析演算法
在主流的商用程式語言[java,C#]的主流實現中都是通過可達性分析來判定物件是否存在的。這個演算法的基本思路就是通過一系列的成為”GC Roots”的物件作為起始點,從這些結點開始向下搜尋,搜尋所經過的路徑成為“引用鏈”,當一個物件到GC Roots沒有任何引用鏈時,則證明此物件是不可用的
在Java語言中, 可作為GC Roots的物件包括下面幾種:
- 虛擬機器棧中引用的物件
- 方法區中類靜態屬性引用的物件
- 方法區中常量引用的物件
- 本地方法棧中JNI[即一般說的Native]引用的物件
再談引用
無論是通過引用計數演算法還是通過可達性分析演算法判斷物件是否存活,都與“引用有關”
在JDK1.2之後,Java對引用的概念進行了補充,將引用分為“強引用”, ”軟引用” , “弱引用”, “虛引用”
- 強引用就是指在程式程式碼中普遍存在的,類似Object o = new Object();這類的引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的物件
- 弱引用就是用來描述一些還有用但並非必須的物件。對於軟引用關聯著的物件,在系統將要發生記憶體溢位之前,將會把這些物件列進回收範圍之中進行第二此回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常
- 弱引用是用來描述非必需物件的,但是它的強度比軟引用更弱,被弱引用關聯的物件只能生存道下一個垃圾收集發生之前
- 虛引用也成為幽靈引用或者幻影引用,它是最弱的一種引用關係。一個物件是否有虛引用的存在完全不會對其生存時間構成影響,也無法通過虛引用取得物件的例項。使用它的唯一目的就是能在這個物件被收集器回收時收到一個系統通知
生存還是死亡
假設在可達性分析演算法中某個物件不可達,它也並非”非死不可”。如果這個物件覆蓋了finalize()方法且這個方法沒有被JVM呼叫過,則JVM會執行finalize()方法。這時你可以在這個方法中重新使某個引用指向該物件。當然,finalize()方法只能救它一次。
回收方法區
方法區的垃圾收集主要回收兩部分內容:
- 廢棄常量
- 無用的類
回收廢棄常量與回收Java堆中的物件非常相似。以常量池中的字面量的回收為例,例如一個字串“abc”已經進入了常量池中,但是當前系統沒有任何一個String物件引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生記憶體回收,而且必要的話,這個“abc”常量就會被系統清理出常量池。常量池中的其它類、介面、方法、欄位的符號引用也與此類似
判定一個類是否是“無用的類”的條件苛刻許多。需要同時滿足下面的三個條件
- 該類的所有例項已經被回收,也就是Java堆中不存在該類的任何例項
- 載入該類的ClassLoader已經被回收
- 該類對應的class物件已經被回收,無法在任何地方通過反射訪問到該類的方法