Java四種引用類型

引用與對象

每種編程語言都有本身操做內存中元素的方式,例如在 C 和 C++ 裏是經過指針,而在 Java 中則是經過「引用」。
在 Java 中一切都被視爲了對象,可是咱們操做的標識符其實是對象的一個引用(reference)。java

//建立一個引用,引用能夠獨立存在,並不必定須要與一個對象關聯
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 內存的前提下,下面的代碼運行正常this

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)

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

與軟引用、弱引用不一樣,虛引用必須和引用隊列一塊兒使用。

相關文章
相關標籤/搜索