Java 的四大引用:強引用、軟引用、弱引用和虛引用

Reference 是一個抽象類,而 SoftReference,WeakReference,PhantomReference 以及 FinalReference 都是繼承它的具體類。

接下來我們來分別介紹和分析強引用以及 java.lang.ref 包下各種虛引用的特性及用法。
StrongReference, SoftReference, WeakReference 以及 PhantomReference 的特性及用法

StrongReference:

我們都知道 JVM 中對象是被分配在堆(heap)上的,當程序行動中不再有引用指向這個對象時,這個對象就可以被垃圾回收器所回收。這裏所說的引用也就是我們一般意義上申明的對象類型的變量(如 String, Object, ArrayList 等),區別於原始數據類型的變量(如 int, short, long 等)也稱爲強引用。

在瞭解虛引用之前,我們一般都是使用強引用來對對象進行引用。如:

1. StrongReference usage
Java代碼  收藏代碼
  1. String tag = new String("T");   

此處的 tag 引用就稱之爲強引用。而強引用有以下特徵:

    強引用可以直接訪問目標對象。
    強引用所指向的對象在任何時候都不會被系統回收。
    強引用可能導致內存泄漏。

我們要討論的這三種 Reference 較之於強引用而言都屬於“弱引用”,也就是他們所引用的對象只要沒有強引用,就會根據條件被 JVM 的垃圾回收器所回收,它們被回收的時機以及用法各不相同。下面分別來進行討論。

SoftReference:

SoftReference 在“弱引用”中屬於最強的引用。SoftReference 所指向的對象,當沒有強引用指向它時,會在內存中停留一段的時間,垃圾回收器會根據 JVM 內存的使用情況(內存的緊缺程度)以及 SoftReference 的 get() 方法的調用情況來決定是否對其進行回收。(後面章節會用幾個實驗進行闡述)

具體使用一般是通過 SoftReference 的構造方法,將需要用弱引用來指向的對象包裝起來。當需要使用的時候,調用 SoftReference 的 get() 方法來獲取。當對象未被回收時 SoftReference 的 get() 方法會返回該對象的強引用。如下:

2. SoftReference usage
Java代碼  收藏代碼
  1. SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name"10));   
  2.  System.out.println(bean.get());// “name:10”  

軟引用有以下特徵:

    軟引用使用 get() 方法取得對象的強引用從而訪問目標對象。
    軟引用所指向的對象按照 JVM 的使用情況(Heap 內存是否臨近閾值)來決定是否回收。
    軟引用可以避免 Heap 內存不足所導致的異常。

當垃圾回收器決定對其回收時,會先清空它的 SoftReference,也就是說 SoftReference 的 get() 方法將會返回 null,然後再調用對象的 finalize() 方法,並在下一輪 GC 中對其真正進行回收。

WeakReference:

WeakReference 是弱於 SoftReference 的引用類型。弱引用的特性和基本與軟引用相似,區別就在於弱引用所指向的對象只要進行系統垃圾回收,不管內存使用情況如何,永遠對其進行回收(get() 方法返回 null)。

完全可以通過和 SoftReference 一樣的方式來操作 WeakReference,這裏就不再複述。

弱引用有以下特徵:

    弱引用使用 get() 方法取得對象的強引用從而訪問目標對象。
    一旦系統內存回收,無論內存是否緊張,弱引用指向的對象都會被回收。
    弱引用也可以避免 Heap 內存不足所導致的異常。

PhantomReference:

PhantomReference 是所有“弱引用”中最弱的引用類型。不同於軟引用和弱引用,虛引用無法通過 get() 方法來取得目標對象的強引用從而使用目標對象,觀察源碼可以發現 get() 被重寫爲永遠返回 null。

那虛引用到底有什麼作用?其實虛引用主要被用來 跟蹤對象被垃圾回收的狀態,通過查看引用隊列中是否包含對象所對應的虛引用來判斷它是否 即將被垃圾回收,從而採取行動。它並不被期待用來取得目標對象的引用,而目標對象被回收前,它的引用會被放入一個 ReferenceQueue 對象中,從而達到跟蹤對象垃圾回收的作用。

所以具體用法和之前兩個有所不同,它必須傳入一個 ReferenceQueue 對象。當虛引用所引用對象被垃圾回收後,虛引用會被添加到這個隊列中。如:

3. PhantomReference usage
Java代碼  收藏代碼
  1. public static void main(String[] args) {   
  2.  ReferenceQueue<String> refQueue = new ReferenceQueue<String>();   
  3.  PhantomReference<String> referent = new PhantomReference<String>(  
  4.      new String("T"), refQueue);   
  5.  System.out.println(referent.get());// null   
  6.   
  7.  System.gc();   
  8.  System.runFinalization();   
  9.   
  10.  System.out.println(refQueue.poll() == referent); //true   
  11.  }   

值得注意的是,對於引用回收方面,虛引用類似強引用不會自動根據內存情況自動對目標對象回收,Client 需要自己對其進行處理以防 Heap 內存不足異常。

虛引用有以下特徵:

    虛引用永遠無法使用 get() 方法取得對象的強引用從而訪問目標對象。
    虛引用所指向的對象在被系統內存回收前,虛引用自身會被放入 ReferenceQueue 對象中從而跟蹤對象垃圾回收。
    虛引用不會根據內存情況自動回收目標對象。

另外值得注意的是,其實 SoftReference, WeakReference 以及 PhantomReference 的構造函數都可以接收一個 ReferenceQueue 對象。當 SoftReference 以及 WeakReference 被清空的同時,也就是 Java 垃圾回收器準備對它們所指向的對象進行回收時,調用對象的 finalize() 方法之前,它們自身會被加入到這個 ReferenceQueue 對象中,此時可以通過 ReferenceQueue 的 poll() 方法取到它們。而 PhantomReference 只有當 Java 垃圾回收器對其所指向的對象真正進行回收時,會將其加入到這個 ReferenceQueue 對象中,這樣就可以追綜對象的銷燬情況。

讓我們來回顧一下四種引用類型的表現以及在垃圾回收器回收清理內存時的表現 .

    軟引用 (SoftReference), 引用類型表現爲當內存接近滿負荷 , 或對象由 SoftReference.get() 方法的調用沒有發生一段時間後 , 垃圾回收器將會清理該對象 . 在運行對象的 finalize 方法前 , 會將軟引用對象加入 ReferenceQueue 中去 .
    弱引用 (WeakReference), 引用類型表現爲當系統垃圾回收器開始回收時 , 則立即會回收該對象的引用 . 與軟引用一樣 , 弱引用也會在運行對象的 finalize 方法之前將弱引用對象加入 ReferenceQueue.
    強引用 (FinalReference), 這是最常用的引用類型 . JVM 系統採用 Finalizer 來管理每個強引用對象 , 並將其被標記要清理時加入 ReferenceQueue, 並逐一調用該對象的 finalize() 方法 .
    虛引用 (PhantomReference), 這是一個最虛幻的引用類型 . 無論是從哪裏都無法再次返回被虛引用所引用的對象 . 虛引用在系統垃圾回收器開始回收對象時 , 將直接調用 finalize() 方法 , 但不會立即將其加入回收隊列 . 只有在真正對象被 GC 清除時 , 纔會將其加入 Reference 隊列中去 .
當多次運行系統垃圾回收後,IBM JVM 將軟引用一併加入了回收隊列中,並運行了其 finalize 方法。另外,即使經過很多次系統垃圾回收,虛引用也沒有被加入到隊列中去。不知道這是不是 IBM JVM 的一個小小的 BUG 所在。 
    SoftReference 中 Oracle JVM 的表現滿足規範,只當內存不足時才進行回收。而 IBM JVM 的策略則更爲積極,在內存尚且充足的情況下也進行了回收,值得注意。
    PhantomReference 中 Oracle JVM 的表現滿足規範,執行 finalize 後若干次 GC 就被添加到了 Queue 中。而 IBM JVM 則始終沒有被添加到 Queue 中導致了死循環。所以在使用 PhantomReference 時出現類似的情況時,可以考慮是否是因爲使用了不同 JVM 所導致。
發佈了47 篇原創文章 · 獲贊 44 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章