Java虛擬機02——對象存活判斷和4種引用

對象存活判斷

垃圾收集器在對堆進行回收前,第一件事情就是要肯定這些對象之中哪些還「存活」着,哪些已經「死去」java

引用計數法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候候計數器爲0的對象是不可能再被使用的。算法

  • 缺點:難以解決對象之間互相循環引用的問題

例子:引用計數算法的缺陷ide

-XX:+PrintGCDetails 經過此命令能夠打印GC信息函數

public class RefrenceCountingGC {
    public Object instance = null;
    private static final int _1MB=1024* 1024;

    private byte[] bigSize = new byte[2 * _1MB];

    public static void testGC() {
        RefrenceCountingGC objA = new RefrenceCountingGC();
        RefrenceCountingGC objB = new RefrenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        // 調用GC
        System.gc();
    }

    public static void main(String[] args) {
        testGC();
    }
}
複製代碼

image.png

上面例子objA與objB互相依賴,從結果來看,內存大小從7014k -> 832k,虛擬機進行了回收,證實虛擬機不是經過引用計數法來判斷存活的。測試

可達性分析算法

經過一系列的成爲「GC Roots」 的對象做爲起點,從這些節點開始向下搜索,當一個對象到GC Roots沒有任何GC鏈鏈接時(從GC Roots到這個對象不可達)則證實這個對象是不可活的。如圖:spa

image.png

GC Roots的對象包括下面幾種code

  • 虛擬機棧(棧幀中的本地變量)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI(Natice方法)引用的對象

4種引用

通過上面描述得知,對象的存活都與「引用」有關。在JDK1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Week Reference)、虛引用(Phantom Reference),引用強度依次減弱cdn

強引用 > 軟引用 > 弱引用 > 虛引用對象

強引用

是使用最廣泛的引用,相似「Object obj = new Object()」。只要強引用還存在,垃圾收集器就不會回收被引用的對象blog

軟引用

描述一些還有用但並不是必須的對象。在系統將要發生內存溢出以前,將會把這些對象列進回收範圍之中進行二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。

弱引用

描述非必須對象,強度比軟引用還弱一些。被弱引用關聯的對象只能生存到下次垃圾收集以前。當垃圾收集器工做時,不管當內存是否足夠,都會回收掉只被弱引用關聯的對象。

虛引用

一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來得到一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是在這個對象被收集回收時收到一個系統通知、

4種引用代碼實踐

判斷對象是否存活

若是經過可達性算法分析一個對象不可達,此時會被第一次標記,而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋該方法或已經被執行了,則不須要執行finalize()方法。

若是斷定須要執行finalize()方法,那這個對象將會放在F-Queue的隊列中,虛擬機會觸發這個方法,但不承諾會等待它運行結束(若是方法緩慢,將致使崩潰)。稍後GC將會對F-Queue進行第二次標記,並把標記的對象移到「即將回收」的集合中。

綜上,finalize()函數是在JVM回收內存時執行的,僅執行一次,但JVM並不保證在回收內存時必定會調用。

測試引用例子

先建立一個Demo類,重寫它的finallize()方法,若是被GC了,則會打印信息

public class RefrenceDemo {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("哎呀,我被回收了");
        super.finalize();
    }
}
複製代碼

測試強引用

測試代碼:

public static void main(String[] args) {
        RefrenceDemo demo = new RefrenceDemo();
        System.gc();
    }
複製代碼

結果: 無任何輸出,則證實強引用對象沒有被回收

測試軟引用

測試代碼:

VM參數: -Xms20m -Xmx20m

public static void main(String[] args) {
        List<String> temp = new ArrayList<>();
        SoftReference<RefrenceDemo> ref = new SoftReference<>(new RefrenceDemo());
        for (int i = 0; i < 10000; i++) {
            temp.add(String.valueOf(i));
        }
        System.gc();
    }	
複製代碼

運行代碼,此時內存充足,並無輸出任何結果

將代碼中的10000改爲100000

public static void main(String[] args) {
        List<String> temp = new ArrayList<>();
        SoftReference<RefrenceDemo> ref = new SoftReference<>(new RefrenceDemo());
        for (int i = 0; i < 100000; i++) {
            temp.add(String.valueOf(i));
        }
        System.gc();
    }
複製代碼

運行結果:

image.png

此時,finalize()方法被執行了,說名內存不足,須要回收軟引用的對象。

測試弱引用

VM參數: -Xms20m -Xmx20m

將上例的10000縮小到1000,此時內存空間是足夠的。

public static void main(String[] args) {
        WeakReference<RefrenceDemo> ref = new WeakReference<>(new RefrenceDemo());

        System.out.println(ref.get());
        
        List<String> temp = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            temp.add(String.valueOf(i));
        }

        System.gc();
    }
複製代碼

運行結果:

image.png

在GC前,引用還存活,GC後便執行了finalize()方法,說明弱引用只能活到GC前

測試虛引用

VM參數: -Xms20m -Xmx20m

public static void main(String[] args) {
        ReferenceQueue queue = new ReferenceQueue();
        PhantomReference<RefrenceDemo> ref = new PhantomReference<>(new RefrenceDemo(),queue);

        System.out.println(ref.get());

        System.gc();
    }

複製代碼

運行結果:

image.png

這說明虛引用在實例化後,就被終止了

相關文章
相關標籤/搜索