1. 程式人生 > >【小家java】引用型別(強引用、軟引用、弱引用、虛引用)

【小家java】引用型別(強引用、軟引用、弱引用、虛引用)

相關閱讀

1、概述

本文不論述java中值傳遞和引用傳遞之間的問題(有需求的可移步理解java中值傳遞和引用傳遞),而重點討論Java中提供了4個級別的引用:強應用、軟引用、弱引用和虛引用。這四個引用定義在java.lang.ref的包下。講述這個話題的原因,也是我第一次在集合框架裡看到WeakHashMap而被帶進來,閒話不多說,直接進入主題~

2、栗子

強引用( Final Reference):只要強引用還存在,垃圾收集器永遠不會回收(JVM寧願丟擲OOM異常也不回收強引用所指向的對)被引用的物件。但是,強引用可能會造成可能導致記憶體洩露哦,這個在後續文章中會有說明

貼出JDK對強引用的原始碼:

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

從類定義中可以看出,只有一個建構函式。重點是:它是非public的類哦,因此我們並不能在外部呼叫此建構函式來create一個強引用呢。這得益於JVM的設計,各位看官可以想想為什麼呢?

軟引用(Soft Reference):是用來描述一些還有用但並非必須的物件。對於軟引用物件,如果記憶體充足gc不會管它,如果記憶體不夠了,它就不能倖免了。在 JDK 1.2 之後,提供了 SoftReference 類可以讓呼叫者建立一個軟引用。軟引用可用來實現記憶體敏感的快取記憶體

。軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。一旦SoftReference儲存了對一個Java物件的軟引用後,在垃圾執行緒對這個Java物件回收前,SoftReference類所提供的get()方法返回Java物件的強引用

先看一個最簡單的使用

 public static void main(String[] args) {
   Obj obj = new Obj(); //建立一個強引用
   SoftReference<Obj> softRef = new
SoftReference<>(obj); softRef.get(); //此方法需要注意:若softRef沒被回收,返回對obj的強引用,若被回收了,則返回null,所以不可靠,適合做快取 }

此時,對於這個Obj物件,有兩個引用路徑,一個是來自SoftReference物件的軟引用,一個來自變數softRef 的強引用,所以這個Obj物件是強可達的,所以softRef.get()永遠是有返回值的。此加如下程式碼:

  ...省略上面程式碼...
  obj = null; //刪除掉強引用

這時,Obj物件就只剩下軟引用了,是軟可達的。這個時候如果用get(),返回值就有可能是null了,因此使用的時候要十分注意。但是Obj物件被回收後又出現問題:SoftReference成垃圾了,完全沒用了,這個時候建議處理掉,否則可能造成記憶體洩漏現象。所以ReferenceQueue佇列上場啦。

物件的記憶體回收的時候會經歷一個過程,從Active->Pending->Enqueued->Inactive。pending狀態就是等待著進入ReferenceQueue佇列的這樣一個狀態,說白了它目前還沒被回收,只是物件的引用(使用者程式碼中的引用)被移除了,pending儲存了這個引用,並且放進ReferenceQueue裡(更詳細的可諮詢JVM的物件回收流程),so

 public static void main(String[] args) {
    //定義一個引用佇列
    ReferenceQueue<Obj> queue = new ReferenceQueue<>();
    //建立一個強引用
    Obj obj = new Obj();
    //建立一個軟引用,並且關聯上引用佇列
    SoftReference<Obj> softRef = new SoftReference<>(obj, queue);
    if ((softRef = (SoftReference<Obj>) queue.poll()) != null) {
        //佇列裡存在 說明物件馬上就要被回收了 所以順勢也把軟引用物件幹掉
        softRef = null; //可參考expungeStaleEntries方法
    }
}

從上可以看出,咱們就可以監聽回收,然後doSomething了

弱引用(WeakReference):弱引用和軟引用很像,當gc時,無論記憶體是否充足,都會回收被弱引用關聯的物件。它也可以和ReferenceQueue配合使用。如果弱引用所引用的物件被JVM回收,這個弱引用就會被加入到與之關聯的引用佇列中

虛引用(關注使用場景)

虛引用(PhantomReference):虛引用和前面的軟引用、弱引用不同,它並不影響物件的生命週期(java物件的生命週期)。一個物件與虛引用關聯,則跟沒有引用與之關聯一樣,所以get()方法永遠返回null,在任何時候都可能被垃圾回收器回收。因此它必須和ReferenceQueue一起使用,否則沒有任何意義

3、使用場景

  • 使用軟引用構建敏感資料的快取(如使用者的基本資訊)
  • 使用弱引用構建非敏感資料的快取,如WeakHashMap 當一個鍵物件被垃圾回收器回收時,那麼相應的值物件的引用會從Map中刪除。WeakHashMap能夠節約儲存空間,可用來快取那些非必須存在的資料。

如果使用全域性Map,必然會造成很大程度上的記憶體洩漏。鑑於軟引用(弱引用)的特點,可以結合ReferenceQueue來實現快取記憶體了,這樣對記憶體也特別友好。下面介紹一個例項演示,讓同學們有個感官上的認識

 public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    //放置1000個1M大小的物件
    for (int i = 0; i < 1000; i++) {
        byte[] bytes = new byte[1024 * 1024 * 8];
        map.put(i, bytes);
    }
    //報錯:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    System.out.println("map.size->" + map.size()); 
}

上面使用了強引用型別,就直接報錯了,這是必然的OOM錯誤

Map<Object, Object> map = new HashMap<>();
for(int i = 0;i < 10000;i++) {
    byte[] bytes = new byte[_1M];
    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
    map.put(weakReference, i);
}
System.out.println("map.size->" + map.size());

這裡使用了weakReference物件,即當值不再被引用時,相應的資料被回收。另外使用一個守護執行緒不斷地從佇列中獲取被gc的資料

Thread thread = new Thread(() -> {
    try {
        int cnt = 0;
        WeakReference<byte[]> k;
        while((k = (WeakReference) referenceQueue.remove()) != null) {
            System.out.println((cnt++) + "回收了:" + k);
        }
    } catch(InterruptedException e) {
        //結束迴圈
    }
});
thread.setDaemon(true);
thread.start();

結果如下:

9992回收了:java.lang.ref.WeakReference@1d13cd4
9993回收了:java.lang.ref.WeakReference@118b73a
9994回收了:java.lang.ref.WeakReference@1865933
9995回收了:java.lang.ref.WeakReference@ad82c
map.size->10000

在這次處理中,map並沒有因為不斷加入的1M物件而產生OOM異常,並且最終size=10000。不過其中的key(即weakReference)物件中的byte[]物件卻被回收了。即不斷new出來的1M陣列被gc掉了。
從列印的結果中,我們看到有9995個物件被gc回收了,意味著在map的key中,除了weakReference之外,沒有我們想要的業務物件(只存在引用物件,不存在真實物件了)。所以這個時候為了節約記憶體,其實是可以把entry一起移除掉的,這裡不做演示了,同學們可以自行試驗

4、最後

咱們最常用的肯定是強引用,但是java提供的另外幾種引用型別也是很有必要了解的,在特殊的場合也非常好用,特別是對於記憶體敏感的一些業務常用,可以極大的提高記憶體使用率提升效率,也可以提升jvm的能力
java4中引用型別對比圖
##—-題後語—-
我的微信公眾號也會持續推送技術乾貨,歡迎下方二維碼掃碼關注獲取。
這裡寫圖片描述
更多內容持續更新中,歡迎關注我的部落格!
有任何問題,可以跟我留言討論,歡迎指正,不勝感激。
有任何疑問,亦可掃碼向我提我喲~