Java拾遺:005 - Java的四種引用類型

簡介

Java中爲了讓程序員能夠本身控制對象生命週期,提供了四種引用方式,都繼承自java.lang.ref.Reference類,它們分別是:強引用、軟引用、弱引用、虛引用。html

強引用(FinalReference / Finalizer)

在Java中像Object obj = new Object();這種形式產生的引用都是強引用。強引用能夠直接訪問引用對象,並且強引用只在存在,那麼引用對象就不會被JVM垃圾回收,即便JVM拋出OOM異常的時候,也不回收強引用引用的對象,因此不正確的使用方式可能會形成內存泄漏。 表示強引用的類java.lang.ref.FinalReference是包級範圍訪問的,因此外部不能直接使用,而其子類java.lang.ref.Finalizer一樣是包級範圍訪問,因此通常程序員不會使用它們。 下面是一段測試代碼,測試強引用對象是否會被GC掉(在VM中增長-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx5m參數,用於打印GC日誌和設置JVM最大內存)。java

// 經過new的方式生成一個強引用
        Object obj = new Object();

        // java.lang.Object@5702b3b1
        System.out.println(obj);

        // 執行GC
        System.gc();

        // 執行GC後,強引用不會被回收
        // java.lang.Object@5702b3b1
        System.out.println(obj);

        // 分配一塊大內存(5M),此時JVM內存耗盡,實際會拋出OOM錯誤,這裏捕獲掉,觀察軟引用引用的對象是否被回收
        // java.lang.OutOfMemoryError: Java heap space
        try {
            byte[] buf = new byte[5 * 1024 * 1024];
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }

        // 即便內存溢出,也不回收強引用對象
        // java.lang.Object@5702b3b1
        System.out.println(obj);

軟引用(SoftReference)

軟引用用於描述有用但不是很重要的對象,這種對象在JVM內存充足時不會被GC掉,但在內存緊張甚至OOM時,會被GC。 軟引用可用來實現內存敏感的高速緩存。軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。程序員

// 將這個對象賦值給軟引用
        SoftReference<Object> sr = new SoftReference<>(new Object());

        // soft_ref_1 = java.lang.Object@155ec9f4
        System.out.println("soft_ref_1 = " + sr.get());

        // 此時對象只有一個軟件引用了,執行GC
        System.gc();

        // 說明在內存充足的狀況下,GC不會回收軟引用對象
        // soft_ref_2 = java.lang.Object@5a6d6fc5
        System.out.println("soft_ref_2 = " + sr.get());

        // 分配一塊大內存(5M),此時JVM內存耗盡,實際會拋出OOM錯誤,這裏捕獲掉,觀察軟引用引用的對象是否被回收
        // java.lang.OutOfMemoryError: Java heap space
        try {
            byte[] buf = new byte[5 * 1024 * 1024];
        } catch (Throwable t) {
            // t.printStackTrace();
        }

        // 說明JVM內存耗盡時會回收軟引用對象
        // soft_ref_3 = null
        System.out.println("soft_ref_3 = " + sr.get());

弱引用(WeakReference)

弱引用是一種比軟引用強度更弱的引用,功能與軟引用相似,但區別在於,只要JVM發生GC,弱引用引用的對象就會被回收。面試

ReferenceQueue<Object> queue = new ReferenceQueue<>();

        // 構造一個弱引用
//        WeakReference<Object> ref = new WeakReference<>(new Object());
        WeakReference<Object> ref = new WeakReference<>(new Object(), queue);

        // weak_ref_1 = java.lang.Object@5702b3b1
        System.out.println("weak_ref_1 = " + ref.get());
        // null
        System.out.println(queue.remove(200L));

        // 此時對象只有一個軟件引用了,執行GC
        System.gc();

        // 即便在內存充足的狀況下,只要發生GC就會回收弱引用對象
        // weak_ref_2 = null
        System.out.println("weak_ref_2 = " + ref.get());
        // 當垃圾對象被回收後,弱引用被加入到引用隊列中
        // java.lang.ref.WeakReference@22927a81
        System.out.println(queue.remove(200L));

        System.gc();
        // null
        System.out.println(queue.remove(200L));

虛引用(PhantomReference)

虛引用和前面的軟引用、弱引用不一樣,它並不影響對象的生命週期,這種引用引用的對象隨時均可能被回收,並且它的get方法永遠返回null。緩存

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

虛引用必須和引用隊列一塊兒使用,用於跟蹤垃圾回收過程。ide

public class PhantomReferenceTest {

    static class MyType {
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("--finalize--");
        }

        @Override
        public String toString() {
            return "--string--";
        }
    }

    @Test
    public void test() throws InterruptedException {

        // 構造虛引用必須傳入引用隊列
        ReferenceQueue<MyType> queue = new ReferenceQueue<>();
        PhantomReference<MyType> ref = new PhantomReference<>(new MyType(), queue);

        // 這種引用跟沒有引用幾乎沒有分別,任什麼時候候經過get方法取得的都是空值
        // phantom_ref_1 = null
        System.out.println("phantom_ref_1 = " + ref.get());

        // 執行GC,第一次:經過觀察輸出日誌發現,JVM找到了垃圾對象,並調用finalize方法回收內存,但沒有當即加入回收隊列
        System.gc();
        System.out.println("GC - 1");
        // --finalize--
        // null
        System.out.println(queue.remove(200L));

        // 執行GC,第二次,經過觀察輸出日誌發現,第二次GC後JVM才真正清除垃圾對象,並將其加入引用隊列(因此才能從隊列中彈出值)
        System.gc();
        System.out.println("GC - 2");
        // java.lang.ref.PhantomReference@32a1bec0
        System.out.println(queue.remove(200L));

        // 執行GC,第三次,後面的GC與該對象已經無關
        System.gc();
        System.out.println("GC - 3");
        // null
        System.out.println(queue.remove(200L));

    }

}

結語

在一次面試中面試官問到了這個問題,當時我只知道強引用和弱引用(其實是軟引用),這種基礎的問題答不上來還真是挺尷尬的。測試

參考資料:spa

相關文章
相關標籤/搜索