Java如何決定對象的生死及對象該如何逃脫?

垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象中哪些還“存活”,哪些已經“死去”。

引用的分類

Java將引用分爲4種:

  • 強引用:指在程序代碼之中普遍存在的引用賦值,即類似 “Object obj = new Object ()”
    這種引用關係。無論何種情況下,只要強引用關係存在,垃圾收集器就不會回收調被引用的對象。

  • 軟引用:用來描述一些還有用,但非必須的對象。只要被軟引用關聯着的對象,在系統將要發生內存溢出異常之前,會把這些對象列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。

  • 弱引用:也是用來描述那些非必須對象,但它的強度比軟引用還弱一些。被弱引用關聯的對象只能生存到下一次垃圾收集發生爲止

  • 虛引用:一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來獲取一個對象實例。

引用計數法

在對象中添加一個引用計數器,每當有一個地方引用它時,計數器就加一;當引用失效時,計數器值就減一;計數器爲零的對象就是不可能再被使用的。
然而,在Java中,並沒有選擇引用計數法來進行內存管理。

可達性分析算法

可達性分析算法的基本思路就是通過一系列稱爲“GC Roots” 的根對象作爲起始節點集,從這些節點開始根據引用關係向下搜索,搜索走過的路徑稱爲“引用鏈”。如果某個對象到GC Roots 之間沒有任何引用鏈,則證明該對象是不可能被使用的。

可達性分析算法
可作爲GC Roots的對象如下:

  • 在虛擬機棧中引用的對象,比如各個線程被調用方法堆棧中的參數、局部變量、臨時變量等
  • 在方法區中類靜態屬性引用的對象,比如引用類型靜態變量
  • 在方法區中常量引用的對象,比如字符串常量池裏的引用
  • 在本地方法棧中JNI(Native)引用的對象
  • Java虛擬機內部的引用,如基本數據類型對應的Class對象,常駐的異常對象
  • 所有被同步鎖(synchronized)持有的對象
  • 反映虛擬機內部情況的JMXBean、JVMTI中註冊的回調、本地代碼緩存等

除了以上外,也可以"臨時"加入一些對象作爲GC Roots對象。(比如在新生代回收時,就需要考慮老年代對象的調用,需要將關聯區域的對象也一併加入到GC Roots中)

死亡逃脫

先看以下代碼

/**
 * 1.對象可以在被GC時自我拯救
 * 2.這種自救的機會只有一次,因爲一個對象的finalize()方法最多隻會被系統自動調用一次
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("I am still alive !");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        // 這裏自救,重新建立關聯
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable{
        SAVE_HOOK = new FinalizeEscapeGC();

        //對象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因爲finalizer 方法優先級很低,暫停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("I am dead!");
        }

        //下面這段代碼與上面的完全相同,但是這次自救卻失敗了
        SAVE_HOOK = null;
        System.gc();
        // 因爲finalizer 方法優先級很低,暫停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("I am dead!");
        }

    }
}

執行結果:
在這裏插入圖片描述
這是爲什麼呢?
第一次成功自救是因爲真正對一個對象回收,需要進行兩次標記
如果對象在進行可達性分析後,發現沒有引用鏈,那它將會被第一次標記,隨後進行一次篩選,篩選的條件是是否有必要執行finalizer 方法,假如對象沒有沒有覆蓋finalizer ()方法,或者finalizer ()已經被調用過,那虛擬機將這兩種情況視爲沒必要執行。第二次不能逃脫,是因爲系統已經執行過finalizer方法了
如果對象被判斷爲有必要執行finalizer()方法,那麼該對象會被放進一個低優先級的隊列F-Queue中,稍後收集器將對F-Queue中的對象進行第二次小規模的標記。第一次逃脫就是因爲在finalizer方法中重新建立了關聯,所以第二次標記時它被移出"即將回收" 的集合

finalizer方法運行代價高昂,不確定性大,無法保證各個對象的調用順序,已經被官方聲明爲不推薦使用的語法。筆者建議大家使用try-finally方式

回收方法區

方法區的垃圾收集主要回收兩部分內容: 廢棄的常量和不再使用的類型

參考:《深入理解Java虛擬機》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章