1. 程式人生 > >GC策略之哪些物件可以被回收

GC策略之哪些物件可以被回收

GC需要解決三件事:

1、哪些物件可以被回收。 2、何時回收這些物件。 3、採用什麼樣的方式回收。

引用計數演算法:

應用案例:微軟的COM技術、使用Action Scrip3的FlashPlayer、Python語言及在遊戲指令碼領域被廣泛應用的Squirrel中。

描述:給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就會+1;當引用失效時,計數器值就-1;任何時刻計數器都為0的物件就是不可能再被使用的。

問題:很難解決物件之間的互相迴圈引用的問題。

public class Object {

    Object field = null;
    
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                Object objectA = new Object();
                Object objectB = new Object();//1
                objectA.field = objectB;
                objectB.field = objectA;//2
                //to do something
                objectA = null;
                objectB = null;//3
            }
        });
        thread.start();
        while (true);
    }
    
}

根搜尋演算法:

應用案例:主流的商用程式語言中(Java、C#、Lisp)

描述:通過一系列的名為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋(搜尋所走過的路徑稱為引用鏈),當一個物件到GC Roots沒有任何引用鏈相連(用圖論的話來說就是從GC Roots到這個物件不可達)時,則證明此物件是不可用的。

在Java語言,可作GC Roots的物件包括下面幾種:

1.虛擬機器棧(棧幀中的本地變量表)中的引用的物件。

2.方法區中的類靜態屬性引用的物件。

3.方法區中的常量引用的物件。

4.本地方法棧中JNI(即一般說的Natice方法)的引用的物件。

finalize()方法:

在根搜尋演算法中不可達的物件,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個物件死亡,至少要經歷兩次標記過程:如果物件在進行根搜尋後發現沒有與 GC Roots 相連線的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行 finalize()方法。當物件沒有覆蓋 finalize() 方法,或者 finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況都視為“沒有必要執行”。如果這個物件被判定為有必要執行 finalize()方法,那麼這個物件將會被放置在一個名為 F-Queue 的佇列之中,並在稍後由一條由虛擬機器自動建立的、低優先順序的 Finalizer 執行緒去執行。這裡所謂的“執行”是指虛擬機器會觸發這個方法,但並不承諾會等待它執行結束。這樣做的原因是,如果一個物件在 finalize()方法中執行緩慢,或者發生了死迴圈(更極端的情況),將很可能會導致 F-Queue 佇列中的其他物件永久處於等待狀態,甚至導致整個記憶體回收系統崩潰。 finalize()方法是物件逃脫死亡命運的最後一次機會,稍後 GC 將對 F-Queue 中的物件進行第二次小規模的標記,如果物件要在 finalize()中成功拯救自己——只要重新與引用鏈上的任何一個物件建立關聯即可,譬如把自己(this 關鍵字)賦值給某個類變數或物件的成員變最,那在第二次標記時它將被移除出“即將回收”的集合;如果物件這時候還沒逃脫,那它就真的離死不遠了。

任何一個物件的finalize()方法都只會被系統自動呼叫一次,如果物件面臨下次回收,它的finalize()不會被再次執行。

參考:《深入理解Java虛擬機器:JVM高階特性與最佳實踐》