JVM學習筆記(四)對象已死嗎

  堆中存放着java中幾乎所有的對象實例,垃圾收集器在堆堆進行回收前,首先要確定這些對象哪些還“活着”,哪些已經“死去”。有如下兩種方法:

引用計數算法

  爲對象添加一個引用計數器,每當有一個地方引用該對象時,則該引用計數器值加1,;當引用失效時,則該引用計數器值減1;最後,計數器爲0的對象就是不可能再被使用的,也即所謂的“死去”的對象。
Java虛擬機中並沒有選用引用計算算法來管理內存,主要原因是很難解決對象間相互循環引用的問題。

public class TestReferenceCountingGC {
    public Object instance = null;    
    public void testGc(){  
        TestReferenceCountingGC objA = new TestReferenceCountingGC();  
        TestReferenceCountingGC objB = new TestReferenceCountingGC();  
        objA.instance = objB;  
        objB.instance = objA;  
        objA = null;  
        objB = null;  

        System.gc();  
    }  
}

在vm arguments中設置 -XX:+PrintGCDetails 的輸出

[GC (System.gc()) [PSYoungGen: 2427K->600K(47104K)] 2427K->600K(155136K), 0.0150407 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (System.gc()) [PSYoungGen: 600K->0K(47104K)] [ParOldGen: 0K->523K(108032K)] 600K->523K(155136K), [Metaspace: 2570K->2570K(1056768K)], 0.0201145 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
Heap
 PSYoungGen      total 47104K, used 404K [0x000000078b780000, 0x000000078ec00000, 0x00000007c0000000)
  eden space 40448K, 1% used [0x000000078b780000,0x000000078b7e5360,0x000000078df00000)
  from space 6656K, 0% used [0x000000078df00000,0x000000078df00000,0x000000078e580000)
  to   space 6656K, 0% used [0x000000078e580000,0x000000078e580000,0x000000078ec00000)
 ParOldGen       total 108032K, used 523K [0x0000000722600000, 0x0000000728f80000, 0x000000078b780000)
  object space 108032K, 0% used [0x0000000722600000,0x0000000722682e08,0x0000000728f80000)
 Metaspace       used 2577K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

  GC日誌中包含“2427K->600K”,意味着虛擬機並沒有因爲這兩個對象相互引用就不回收他們,側面說明虛擬機並不是通過引用計數算法來判斷對象是否存活的。

可達性分析算法

  算法基本思想:通過一系列名爲”GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,下圖對象object5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達的,所以它們將會判定爲是可回收對象。
這裏寫圖片描述

在java語言中,可作爲GC Roots的對象包括下面幾種:

  1. 虛擬機棧(幀棧中的本地變量表)中引用的對象
  2. 方法區中類靜態屬性引用的對象
  3. 方法區中常量引用的對象
  4. 本地方法棧中JNI(Native方法)引用的對象

引用的分類

  • 強引用(Strong Reference)
    程序代碼中普遍存在的,類似“Object obj = new Object()”,只要存在強引用,GC收集器永遠不會回收被引用的對象。
  • 軟引用(Soft Reference)
    非必須的對象,是否回收,要看當前內存情況,如果緊張,則進行回收,否則不回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。
  • 弱引用(Weak Reference)
    無論內存是否足夠, 被弱引用關聯的對象只能生存到下一次GC發生之前。即每次必被回收。
  • 虛引用(Phantom Reference)
    一個對象是否有虛引用的存在,不會影響到其生存時間,也無法通過虛引用獲取對象實例。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被回收的時候收到一個系統通知。

生存還是死亡

  即使在可達性分析算法中不可達的對象 ,也並非是“非死不可”的,要真正宣告一個對象死亡,至少要經歷兩次標記過程:

  1. 當沒有發現引用鏈時,進行第一次標記並且進行一次篩選,篩選的條件是對象是否有必要執行finalize方法。當對象沒有覆蓋finalize方法,或者finalize方法已經被JVM調用過,此種情況下認爲沒有必要執行finalize方法。

  2. 如果這個對象有必要執行finalize方法,此時對象會被放置在一個F-Queue隊列中,然後虛擬機會自動建立一個低優先級的Finalizer線程去觸發finalize方法。finalize方法是對象逃脫死亡命運的最後一次機會。此時,GC會對隊列中的對象進行第二次標記,如果對象在finalize方法中完成了自救,只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,則在第二次標記時該對象將被移出“即將回收”的集合。否則,只能判定對象死了。

一次對象自我拯救的演示

public class FinalizeEscapeGC {

    public static FinalizeEscapeGC obj = null;


    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        FinalizeEscapeGC.obj = this;//與GC Roots的任何一個對象建立關聯
    }

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


        //對象第一次成功拯救自己,對象覆蓋了finalize()方法,判定爲有必要執行finalize()方法,回收前成功逃脫
        obj = null;
        System.gc();
        Thread.sleep(500);
        if(obj != null){
            System.out.println("i am still alive");
        }else{
            System.out.println("i am dead");
        }


        //自救失敗,因爲finalize()方法已經在上邊調用過了,不會再調用finalize()方法了
        obj = null;
        System.gc();
        Thread.sleep(500);
        if(obj != null){
            System.out.println("i am still alive");
        }else{
            System.out.println("i am dead");
        }
    }
}

運行結果

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