1. 程式人生 > >垃圾回收——物件已死?

垃圾回收——物件已死?

  在瞭解了JVM 的記憶體區域分佈後,我們知道幾乎所有的例項物件都在堆上存放,垃圾收集器在對堆進行垃圾回收之前,第一件事就是判斷哪些物件是“存活”,哪些已經“死去”(即不可能再被任何途徑使用的物件)。那麼JVM 該如何判斷呢?

一、 談談引用

  在回收“死亡”物件之前,我們需要對物件的“死亡”下一個定義——當一個物件不存在任何引用的時候,稱為死亡。所以在介紹接下來來的內容之前我們需要談談引用。

在JDK 1.2 之前,Java 中引用的定義很傳統:
如果reference 型別的資料中儲存的數值代表的是另一塊記憶體的起始地址,就稱這塊記憶體地址代表一個引用。

這樣的定義很純粹,但不全面。這種定義下一個物件就只存在有引用和沒有引用兩種狀態。在前面的定義下,大量的物件會被判斷為“死亡”,這樣對一些“食之無味棄之可惜”的物件來說顯得太過無情(亦或說太過頻繁的GC 不是我們希望看到的情況)。就像人有惰性,事到不可不為才為之一樣,我們希望描述這樣的物件:當記憶體空間足夠時,保留;當GC 過後記憶體空間不足時,拋棄。
  在JDK1.2 之後Java 對引用的概念進行了擴充,將引用分為:強引用(Strong reference)、軟引用(Soft reference)、弱引用(Weak reference)、虛引(Phantom reference)用四種,引用強度依次遞減。

  • 強引用是使用最普遍的引用。如果一個物件具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足的問題。 ps:強引用其實也就是我們平時A a = new A()這個意思。

  • 軟引用用來描述一些非必須物件。在系統將要發生記憶體洩露異常之前,將會把這些物件列入回收範圍,如果還不足,才報異常。軟引用主要應用在使用者實現快取。JDK1.2 之後提供了SoftReference 類來實現軟引用。
    Object obj = new Object();
    SoftReference sf = new SoftReference(obj);

  • 弱引用也是用來描述非必須物件,它的程度比軟引用更弱,只能存活到下一次GC 之前。弱引用主要用於監控物件是否已經被垃圾回收器標記為即將回收的垃圾。JDK1.2 之後提供了WeakReference 類來實現弱引用。
    Object obj = new Object();
    WeakReference wf = new WeakReference(obj);

  • 虛引用,也稱幽靈引用和幻影引用。它根本取不到物件的值,它的唯一目的就是能在這個物件被GC 時收到一個系統通知。虛引用主要用於檢測物件是否已經從記憶體中刪除。JDK1.2 之後提供了PhantomReference 類來實現虛引用。
    Object obj = new Object();
    PhantomReference pf = new PhantomReference(obj);

關於引用的詳細介紹參考這個部落格:Java四種引用包括強引用,軟引用,弱引用,虛引用。
因為物件是否能夠被回收靠它是否被引用來判斷,所以這裡花了一些篇幅來介紹引用。

二、 判斷物件是否死亡的演算法

  在介紹完引用的概念後,我們開始看看判斷物件是否死亡的演算法 ,你會知道判定物件是否存活都與“引用”有關。

(1) 引用計數演算法

什麼是引用計數法?

給物件新增一個引用計數器,每當有一個物件引用它,計數器就加1 ;任何時刻計數器為0 的物件就是不可能再被使用的。

這個演算法很優秀,但是很難解決物件相互引用的問題,在主流的Java 虛擬機器中沒有采用這個演算法。

public class Test{
    public Object instance = null;
    ...
}
Test A = new Test();
Test B = new Test();
A.instance = B;
B.instance = A;
A = null;
B = null;
System.gc();

這裡兩個物件的引用都不為0 ,但是會被GC。

(2) 可達性分析演算法

可達性演算法應用在目前主流的程式語言中(Java、c#等),這個演算法的主要思路就是通過一系列的稱為“GC Root” 的物件作為起點,從這些節點開始往下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Root 沒有任何引用鏈相連,則證明此物件是不可用的。
這裡寫圖片描述
如圖object7、object8、object9就是可被回收物件。
在Java 語言中可被作為GC Root 的物件包含:

  • 虛擬機器棧(棧幀中的本地變量表)中的物件
  • 方法區中類靜態屬性引用物件
  • 方法區中常量引用的物件
  • 本地方法棧中(一般說的Native)引用的物件

三、回收方法區

永久代的垃圾回收主要回收兩個部分的內容:廢棄常量和無用的類。回收廢棄常量和回收Java 堆中的類非常類似。主要是回收無用的類。要回收無用的類必須滿足3個條件:

  • 該類所有的例項都已經被回收,也就是Java 堆中不存在該類的任何例項
  • 載入該類的ClassLoader 已經被回收
  • 該類對應的java.lang.Class 物件在任何地方都沒有引用,無法通過反射訪問該類

虛擬機器可以對滿足上述3個條件的無用類進行回收,“可以”不代表一定要回收。只在在大量使用反射、動態代理、GCLib 等ByteCode 框架、動態生成JSP 以及OSGi 這類頻繁自定義ClassLoader 的場景都需要虛擬機器具備解除安裝的功能,以保證永久代不會溢位。