1. 程式人生 > >Java四種引用類型

Java四種引用類型

c++ 依據 回收算法 主動 size 存在 list 創建 字節

引用與對象

每種編程語言都有自己操作內存中元素的方式,例如在 C 和 C++ 裏是通過指針,而在 Java 中則是通過“引用”。
在 Java 中一切都被視為了對象,但是我們操作的標識符實際上是對象的一個引用(reference)。

//創建一個引用,引用可以獨立存在,並不一定需要與一個對象關聯
String s;

通過將這個叫“引用”的標識符指向某個對象,之後便可以通過這個引用來實現操作對象了。

String str = new String("abc");
System.out.println(str.toString());

在 JDK1.2 之前,Java中的定義很傳統:如果 reference 類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱為這塊內存代表著一個引用。
Java 中的垃圾回收機制在判斷是否回收某個對象的時候,都需要依據“引用”這個概念。
在不同垃圾回收算法中,對引用的判斷方式有所不同:

  • 引用計數法:為每個對象添加一個引用計數器,每當有一個引用指向它時,計數器就加1,當引用失效時,計數器就減1,當計數器為0時,則認為該對象可以被回收(目前在Java中已經棄用這種方式了)。
  • 可達性分析算法:從一個被稱為 GC Roots 的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。

JDK1.2 之前,一個對象只有“已被引用”和"未被引用"兩種狀態,這將無法描述某些特殊情況下的對象,比如,當內存充足時需要保留,而內存緊張時才需要被拋棄的一類對象。

四種引用類型

所以在 JDK.1.2 之後,Java 對引用的概念進行了擴充,將引用分為了:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4 種,這 4 種引用的強度依次減弱。

一,強引用

Java中默認聲明的就是強引用,比如:

Object obj = new Object(); //只要obj還指向Object對象,Object對象就不會被回收
obj = null;  //手動置null

只要強引用存在,垃圾回收器將永遠不會回收被引用的對象,哪怕內存不足時,JVM也會直接拋出OutOfMemoryError,不會去回收。如果想中斷強引用與對象之間的聯系,可以顯示的將強引用賦值為null,這樣一來,JVM就可以適時的回收對象了

二,軟引用

軟引用是用來描述一些非必需但仍有用的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,才會拋出內存溢出異常

。這種特性常常被用來實現緩存技術,比如網頁緩存,圖片緩存等。
在 JDK1.2 之後,用java.lang.ref.SoftReference類來表示軟引用。

下面以一個例子來進一步說明強引用和軟引用的區別:
在運行下面的Java代碼之前,需要先配置參數 -Xms2M -Xmx3M,將 JVM 的初始內存設為2M,最大可用內存為 3M。

首先先來測試一下強引用,在限制了 JVM 內存的前提下,下面的代碼運行正常

public class TestOOM {
    
    public static void main(String[] args) {
         testStrongReference();
    }
    private static void testStrongReference() {
        // 當 new byte為 1M 時,程序運行正常
        byte[] buff = new byte[1024 * 1024 * 1];
    }
}

但是如果我們將

byte[] buff = new byte[1024 * 1024 * 1];

替換為創建一個大小為 2M 的字節數組

byte[] buff = new byte[1024 * 1024 * 2];

則內存不夠使用,程序直接報錯,強引用並不會被回收
技術分享圖片

接著來看一下軟引用會有什麽不一樣,在下面的示例中連續創建了 10 個大小為 1M 的字節數組,並賦值給了軟引用,然後循環遍歷將這些對象打印出來。

public class TestOOM {
    private static List<Object> list = new ArrayList<>();
    public static void main(String[] args) {
         testSoftReference();
    }
    private static void testSoftReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }
        
        System.gc(); //主動通知垃圾回收
        
        for(int i=0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }
        
    }
    
}

打印結果:
技術分享圖片

我們發現無論循環創建多少個軟引用對象,打印結果總是只有最後一個對象被保留,其他的obj全都被置空回收了。
這裏就說明了在內存不足的情況下,軟引用將會被自動回收。
值得註意的一點 , 即使有 byte[] buff 引用指向對象, 且 buff 是一個strong reference, 但是 SoftReference sr 指向的對象仍然被回收了,這是因為Java的編譯器發現了在之後的代碼中, buff 已經沒有被使用了, 所以自動進行了優化。
如果我們將上面示例稍微修改一下:

    private static void testSoftReference() {
        byte[] buff = null;

        for (int i = 0; i < 10; i++) {
            buff = new byte[1024 * 1024];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }

        System.gc(); //主動通知垃圾回收
        
        for(int i=0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }

        System.out.println("buff: " + buff.toString());
    }

則 buff 會因為強引用的存在,而無法被垃圾回收,從而拋出OOM的錯誤。
技術分享圖片

如果一個對象惟一剩下的引用是軟引用,那麽該對象是軟可及的(softly reachable)。垃圾收集器並不像其收集弱可及的對象一樣盡量地收集軟可及的對象,相反,它只在真正 “需要” 內存時才收集軟可及的對象。

三,弱引用

弱引用的引用強度比軟引用要更弱一些,無論內存是否足夠,只要 JVM 開始進行垃圾回收,那些被弱引用關聯的對象都會被回收。在 JDK1.2 之後,用 java.lang.ref.WeakReference 來表示弱引用。
我們以與軟引用同樣的方式來測試一下弱引用:

    private static void testWeakReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];
            WeakReference<byte[]> sr = new WeakReference<>(buff);
            list.add(sr);
        }
        
        System.gc(); //主動通知垃圾回收
        
        for(int i=0; i < list.size(); i++){
            Object obj = ((WeakReference) list.get(i)).get();
            System.out.println(obj);
        }
    }

打印結果:
技術分享圖片

可以發現所有被弱引用關聯的對象都被垃圾回收了。

四,虛引用

虛引用是最弱的一種引用關系,如果一個對象僅持有虛引用,那麽它就和沒有任何引用一樣,它隨時可能會被回收,在 JDK1.2 之後,用 PhantomReference 類來表示,通過查看這個類的源碼,發現它只有一個構造函數和一個 get() 方法,而且它的 get() 方法僅僅是返回一個null,也就是說將永遠無法通過虛引用來獲取對象,虛引用必須要和 ReferenceQueue 引用隊列一起使用。

public class PhantomReference<T> extends Reference<T> {
    /**
     * Returns this reference object‘s referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

那麽傳入它的構造方法中的 ReferenceQueue 又是如何使用的呢?

五,引用隊列(ReferenceQueue)

引用隊列可以與軟引用、弱引用以及虛引用一起配合使用,當垃圾回收器準備回收一個對象時,如果發現它還有引用,那麽就會在回收對象之前,把這個引用加入到與之關聯的引用隊列中去。程序可以通過判斷引用隊列中是否已經加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣就可以在對象被回收之前采取一些必要的措施。

與軟引用、弱引用不同,虛引用必須和引用隊列一起使用。

Java四種引用類型