深入探讨 java.lang.ref 包
http://www.ibm.com/developerworks/cn/java/j-lo-langref/
概述
Java.lang.ref 是 Java 類庫中比較特殊的一個包,它提供了與 Java 垃圾回收器密切相關的引用類。這些引用類對象可以指向其它對象,但它們不同于一般的引用,因為它們的存在并不防礙 Java 垃圾回收器對它們所指向的對象進行回收。其好處就在于使者可以保持對使用對象的引用,同時 JVM 依然可以在內存不夠用的時候對使用對象進行回收。因此這個包在用來實現與緩存相關的應用時特別有用。同時該包也提供了在對象的“可達”性發生改變時,進行提醒的機制。本文通過對該包進行由淺入深的介紹與分析,使讀者可以加深對該包的理解,從而更好地利用該包進行開發。
回頁首
java.lang.ref 包的介紹
我們可以先來看一下 java.lang.ref 這個包的結構,如圖 1 所示
圖 1. java.lang.ref 包結構
?
該包中各類的繼承關系如圖 2 所示
圖 2. java.lang.ref 包中類的繼承關系 :
?
Reference 是一個抽象類,而 SoftReference,WeakReference,PhantomReference 以及 FinalReference 都是繼承它的具體類。
接下來我們來分別介紹和分析強引用以及 java.lang.ref 包下各種虛引用的特性及用法。
回頁首
StrongReference, SoftReference, WeakReference 以及 PhantomReference 的特性及用法
StrongReference:
我們都知道 JVM 中對象是被分配在堆(heap)上的,當程序行動中不再有引用指向這個對象時,這個對象就可以被垃圾回收器所回收。這里所說的引用也就是我們一般意義上申明的對象類型的變量(如 String, Object, ArrayList 等),區別于原始數據類型的變量(如 int, short, long 等)也稱為強引用。
在了解虛引用之前,我們一般都是使用強引用來對對象進行引用。如:
清單 1. StrongReference usage
| String tag = new String("T"); |
此處的 tag 引用就稱之為強引用。而強引用有以下特征:
- 強引用可以直接訪問目標對象。
- 強引用所指向的對象在任何時候都不會被系統回收。
- 強引用可能導致內存泄漏。
我們要討論的這三種 Reference 較之于強引用而言都屬于“弱引用”,也就是他們所引用的對象只要沒有強引用,就會根據條件被 JVM 的垃圾回收器所回收,它們被回收的時機以及用法各不相同。下面分別來進行討論。
SoftReference:
SoftReference 在“弱引用”中屬于最強的引用。SoftReference 所指向的對象,當沒有強引用指向它時,會在內存中停留一段的時間,垃圾回收器會根據 JVM 內存的使用情況(內存的緊缺程度)以及 SoftReference 的 get() 方法的調用情況來決定是否對其進行回收。(后面章節會用幾個實驗進行闡述)
具體使用一般是通過 SoftReference 的構造方法,將需要用弱引用來指向的對象包裝起來。當需要使用的時候,調用 SoftReference 的 get() 方法來獲取。當對象未被回收時 SoftReference 的 get() 方法會返回該對象的強引用。如下:
清單 2. SoftReference usage
| SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10)); 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
| public static void main(String[] args) { ReferenceQueue<String> refQueue = new ReferenceQueue<String>(); PhantomReference<String> referent = new PhantomReference<String>(new String("T"), refQueue); System.out.println(referent.get());// null System.gc(); System.runFinalization(); System.out.println(refQueue.poll() == referent); //true } |
值得注意的是,對于引用回收方面,虛引用類似強引用不會自動根據內存情況自動對目標對象回收,Client 需要自己對其進行處理以防 Heap 內存不足異常。
虛引用有以下特征:
- 虛引用永遠無法使用 get() 方法取得對象的強引用從而訪問目標對象。
- 虛引用所指向的對象在被系統內存回收前,虛引用自身會被放入 ReferenceQueue 對象中從而跟蹤對象垃圾回收。
- 虛引用不會根據內存情況自動回收目標對象。
另外值得注意的是,其實 SoftReference, WeakReference 以及 PhantomReference 的構造函數都可以接收一個 ReferenceQueue 對象。當 SoftReference 以及 WeakReference 被清空的同時,也就是 Java 垃圾回收器準備對它們所指向的對象進行回收時,調用對象的 finalize() 方法之前,它們自身會被加入到這個?ReferenceQueue 對象中,此時可以通過 ReferenceQueue 的 poll() 方法取到它們。而 PhantomReference 只有當 Java 垃圾回收器對其所指向的對象真正進行回收時,會將其加入到這個?ReferenceQueue 對象中,這樣就可以追綜對象的銷毀情況。
各種引用類型總結:
下表對于各種引用類型的特征進行了小結:
表 1. 引用類型特性總結
| 強引用 | 直接調用 | 不回收 | 可能 |
| 軟引用 | 通過 get() 方法 | 視內存情況回收 | 不可能 |
| 弱引用 | 通過 get() 方法 | 永遠回收 | 不可能 |
| 虛引用 | 無法取得 | 不回收 | 可能 |
注意:
如果想使用這些相對強引用來說較弱的引用來進行對象操作的時候,就必須保證沒有強引用指向被操作對象。否則將會被視為強引用指向,不會具有任何的弱引用的特性。
下一章我們將做 2 個實驗來佐證上面這些總結的內容。
StrongReference, SoftReference, WeakReference 以及 PhantomReference 的各種特性實驗分析
為了更好的描述它們的特性,先以表格進行歸納,再以示例程序加以說明。
- JVM 使用 Oracle 的 Java SE6
- 首先將 JVM 運行環境的初始以及最大 Heap 數設到最低以便更明顯的看出結果:
圖 3. 設置 JVM 運行環境初始值
?
接下來就開始我們的實驗。
表 2:各個引用在 GC 后是否被回收?
| StrongReference | 不回收 | 見清單 3 | name:10 |
| SoftReference | 不回收 | 見清單 4 | name:10 |
| WeakReference | 回收 | 見清單 5 | name:10 |
| PhantomReference | N/A | N/A | N/A |
清單 4
| public static void main(String[] args) { Bean bean = new Bean("name", 10); System.gc(); System.runFinalization(); System.out.println(bean);// “name:10”} |
總結:強引用所指向的對象在任何時候都不會被系統回收。結果輸入
清單 5
| public static void main(String[] args) { SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10)); System.gc(); System.runFinalization(); System.out.println(bean.get());// “name:10”} |
總結:軟引用所指向的對象會根據內存使用情況來決定是否回收,這里內存還充足,所以不會被回收。
清單 6
| public static void main(String[] args) { WeakReference<Bean> bean = new WeakReference<Bean>(new Bean("name", 10)); System.gc(); System.runFinalization(); System.out.println(bean.get());// “null”} |
總結:弱引用所指向的對象只要進行 GC,就會自動進行回收,get() 返回 null。
表 3:各個引用創建大量對象時是否導致 Heap 不足異常?
| StrongReference | 拋出異常 | 見清單 6 | Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
| SoftReference | 不拋異常,之前的引用自動清空并返回 null | 見清單 7 | null |
| WeakReference | 同上 | 見清單 8 | null |
| PhantomReference | 拋出異常 | 見清單 9 | Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
清單 7
| public static void main(String[] args) { Bean[] referent = new Bean[100000]; for (int i=0;i<referent.length;i++){ referent[i] = new Bean("mybean:" + i,100);// 拋 Exception } } |
總結:在新開辟 100000 個 Bean 對象時,由于強引用永遠不會被系統回收,當最大 Heap 閾值達到 2m 時,系統就會報出 Heap 不足的異常。
清單 8
| public static void main(String[] args) { Reference<Bean>[] referent = new SoftReference[100000]; for (int i=0;i<referent.length;i++){ referent[i] = new SoftReference<Bean>(new Bean("mybean:" + i,100)); } System.out.println(referent[100].get());// “null”} |
總結:在新開辟 100000 個 Bean 對象時,由于軟引用會視內存使用情況來判斷是否自動回收,所以當最大 Heap 閾值達到 2m 時,系統自動回收最前面開辟的對象,取第 100 個對象時,返回為 null。
清單 9
| public static void main(String[] args) { Reference<Bean>[] referent = new WeakReference[100000]; for (int i=0;i<referent.length;i++){ referent[i] = new WeakReference<Bean>(new Bean("mybean:" + i,100)); } System.out.println(referent[100].get());// “null”} |
總結:WeakReference 與 SoftReference 具有相同的特性,也會視內存使用情況來判斷是否自動回收。取第 100 個對象時,返回為 null。
清單 10
| public static void main(String[] args) { Reference<Bean>[] referent = new PhantomReference[100000]; ReferenceQueue<Bean> queue = new ReferenceQueue<Bean>(); for (int i=0;i<referent.length;i++){ referent[i] = new PhantomReference<Bean>(new Bean("mybean:" + i,100), queue);// throw Exception } System.out.println(referent[100].get()); } |
總結:PhantomReference 類似強引用,它不會自動根據內存情況自動對目標對象回收,所以這里在 Heap 里不斷開辟新空間,當達到 2m 閾值時,系統報出 OutOfMemoryError 異常。
回頁首
FinalReference 以及 Finzlizer
FinalReference 作為 java.lang.ref 里的一個不能被公開訪問的類,又起到了一個什么樣的作用呢?作為他的子類, Finalizer 又在垃圾回收機制里扮演了怎么樣的角色呢?
實際上,FinalReference 代表的正是 Java 中的強引用,如這樣的代碼 :
Bean bean = new Bean();
在虛擬機的實現過程中,實際采用了 FinalReference 類對其進行引用。而 Finalizer,除了作為一個實現類外,更是在虛擬機中實現一個 FinalizerThread,以使虛擬機能夠在所有的強引用被解除后實現內存清理。
讓我們來看看 Finalizer 是如何工作的。首先,通過聲明 FinalizerThread,并將該線程實例化,設置為守護線程后,加入系統線程中去。
清單 11
| static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread finalizer = new FinalizerThread(tg); finalizer.setPriority(Thread.MAX_PRIORITY - 2); finalizer.setDaemon(true); finalizer.start(); } |
在 GC 的過程中,當一個強引用被釋放,由系統垃圾收集器標記后的對象,會被加入 Finalizer 對象中的 ReferenceQueue 中去,并調用 Finalizer.runFinalizer() 來執行對象的 finalize 方法。
清單 12
| private void runFinalizer() { synchronized (this) { if (hasBeenFinalized()) return; remove(); } try { Object finalizee = this.get(); if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { invokeFinalizeMethod(finalizee); /* 注意,這里需要清空棧中包含該變量的的 slot, ** 從而來減少因為一個保守的 GC 實現所造成的變量未被回收的假象 */ finalizee = null; } } catch (Throwable x) { } super.clear(); } |
注意,標記處所調用的 invokeFinalizeMethod 為 native 方法,由于 finalize 方法在 Object 類中被聲明為 protected,這里必須采用 native 方法才能調用。隨后通過將本地強引用設置為空,以便使垃圾回收器清理內存。
可以看到,通過這樣的方法,Java 將四種引用對象類型:軟引用 (SoftReference),弱引用 (WeakReference),強引用 (FinalReference),虛引用 (PhantomReference) 平等地對待,并在垃圾回收器中進行統一調度和管理。
回頁首
不同 Java 虛擬機上的表現與分析
讓我們來回顧一下四種引用類型的表現以及在垃圾回收器回收清理內存時的表現 .
這里比較兩個比較典型的 JVM 環境,Oracle Java SE6 和 IBM JDK 6。采用了如下的測試代碼 :
清單 13. 類 RefTestObj
| public class RefTestObj { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { return super.toString() + "[id=" + this.id + "]"; } @Override protected void finalize() { System.out.println("Object [" + this.hashCode() + "][id=" + this.id + "] come into finalize"); try { super.finalize(); } catch (Throwable e) { e.printStackTrace(); } } } |
清單 14. 類 RefMainThread
| import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; public class RefMainThread { public static void main(String[] args) { // 創建三種不同的引用類型所需對象RefTestObj softRef = new RefTestObj(); RefTestObj weakRef = new RefTestObj(); RefTestObj phanRef = new RefTestObj(); softRef.setId(1); weakRef.setId(2); phanRef.setId(3); ReferenceQueue<RefTestObj> softRefQueue = new ReferenceQueue<RefTestObj>(); ReferenceQueue<RefTestObj> weakRefQueue = new ReferenceQueue<RefTestObj>(); ReferenceQueue<RefTestObj> phanRefQueue = new ReferenceQueue<RefTestObj>(); SoftReference<RefTestObj> softRefObj = new SoftReference<RefTestObj>(softRef, softRefQueue); WeakReference<RefTestObj> weakRefObj = new WeakReference<RefTestObj>(weakRef, weakRefQueue); PhantomReference<RefTestObj> phanRefObj = new PhantomReference<RefTestObj>(phanRef, phanRefQueue); // 打印正常情況下三種對象引用print(softRefObj); print(weakRefObj); print(phanRefObj); // 將對象清空softRef = null; weakRef = null; phanRef = null; // 打印引用隊列及 get() 方法所能取到的對象自身if (softRefObj != null) { System.out.println("Soft Reference Object run get():" + softRefObj.get()); System.out.println("Check soft queue:" + softRefQueue.poll()); } if (weakRefObj != null) { System.out.println("Weak Reference Object run get():" + weakRefObj.get()); System.out.println("Check weak queue:" + weakRefQueue.poll()); } if (phanRefObj != null) { System.out.println("Phantom Reference Object run get():" + phanRefObj.get()); System.out.println("Check Phantom queue:" + phanRefQueue.poll()); } // 開始執行垃圾回收System.gc(); System.runFinalization(); // 檢查隊列,是否已經被加入隊列,是否還能取回對象if (softRefObj != null) { System.out.println("Soft Reference Object run get():" + softRefObj.get()); System.out.println("Check soft queue:" + softRefQueue.poll()); } if (weakRefObj != null) { System.out.println("Weak Reference Object run get():" + weakRefObj.get()); System.out.println("Check weak queue:" + weakRefQueue.poll()); } if (phanRefObj != null) { System.out.println("Phantom Reference Object run get():" + phanRefObj.get()); System.out.println("Check Phantom queue:" + phanRefQueue.poll()); } // 對于虛引用對象,在經過多次 GC 之后, 才會加入到隊列中去Reference<? extends RefTestObj> mynewphan = null; int refCount = 1; while (mynewphan == null) { mynewphan = phanRefQueue.poll(); System.gc(); System.runFinalization(); if (mynewphan != null) { System.out.println("Check Phantom queue:" + mynewphan); System.out.println("Count for " + refCount + " times"); break; } refCount ++; } } public static void print(Reference<RefTestObj> ref) { RefTestObj obj = ref.get(); System.out.println("The Reference is " + ref.toString() + " and with object " + obj +" which is " + (obj == null ? "null" : "not null")); } } |
通過執行 RefMainThread, 我們可以清晰地根據打印結果看到對象在內存中被加入隊列 , 以及調用 finalize 方法的順序及過程 .
為了測試不同的 JVM 環境并消除其他因素的印象 , 本例采用的背景環境均為 Windows2003 下的 32bit JVM.
首先采用了環境為 Oracle Java SE 6 update 23 進行測試 , 結果如下 :
清單 15. Oracle Java SE 6 update 23 下測試結果
| The Reference is java.lang.ref.SoftReference@c17164 and with object RefTestObj@1fb8ee3[id=1] which is not null The Reference is java.lang.ref.WeakReference@61de33 and with object RefTestObj@14318bb[id=2] which is not null The Reference is java.lang.ref.PhantomReference@ca0b6 and with object null which is null Soft Reference Object run get():RefTestObj@1fb8ee3[id=1] Check soft queue:null Weak Reference Object run get():RefTestObj@14318bb[id=2] Check weak queue:null Phantom Reference Object run get():null Check Phantom queue:null Object [27744459][id=3] come into finalize Object [21174459][id=2] come into finalize Soft Reference Object run get():RefTestObj@1fb8ee3[id=1] Check soft queue:null Weak Reference Object run get():null Check weak queue:java.lang.ref.WeakReference@61de33 Phantom Reference Object run get():null Check Phantom queue:null Check Phantom queue:java.lang.ref.PhantomReference@ca0b6 Count for 2 times |
可以看到 , 當運行了系統回收后 , 虛引用與弱引用被回收 , 由于內存并不吃緊 , 軟引用依然保持原樣 . 弱引用立即被加入了隊列 , 而虛引用則在循環兩次的手動調用 GC 后被加入了隊列 . 其次 , 采用的環境是 IBM JDK 6, 結果如下 :
清單 16. IBM JDK 6 下測試結果
| The Reference is java.lang.ref.SoftReference@3a2c3a2c and with object RefTestObj@391e391e[id=1] which is not null The Reference is java.lang.ref.WeakReference@3a303a30 and with object RefTestObj@39203920[id=2] which is not null The Reference is java.lang.ref.PhantomReference@3a353a35 and with object null which is null Soft Reference Object run get():RefTestObj@391e391e[id=1] Check soft queue:null Weak Reference Object run get():RefTestObj@39203920[id=2] Check weak queue:null Phantom Reference Object run get():null Check Phantom queue:null Object [958544162][id=3] come into finalize Object [958413088][id=2] come into finalize Soft Reference Object run get():RefTestObj@391e391e[id=1] Check soft queue:null Weak Reference Object run get():null Check weak queue:java.lang.ref.WeakReference@3a303a30 Phantom Reference Object run get():null Check Phantom queue:null Object [958282014][id=1] come into finalize ............ |
程序運行到這里進入了無限循環,必須手動終止。比對上下兩份結果可以看到,當多次運行系統垃圾回收后,IBM JVM 將軟引用一并加入了回收隊列中,并運行了其 finalize 方法。另外,即使經過很多次系統垃圾回收,虛引用也沒有被加入到隊列中去。不知道這是不是 IBM JVM 的一個小小的 BUG 所在。
結論
- SoftReference 中 Oracle JVM 的表現滿足規范,只當內存不足時才進行回收。而 IBM JVM 的策略則更為積極,在內存尚且充足的情況下也進行了回收,值得注意。
- PhantomReference 中 Oracle JVM 的表現滿足規范,執行 finalize 后若干次 GC 就被添加到了 Queue 中。而 IBM JVM 則始終沒有被添加到 Queue 中導致了死循環。所以在使用 PhantomReference 時出現類似的情況時,可以考慮是否是因為使用了不同 JVM 所導致。
回頁首
小結
本文深入地介紹了 java.lang.ref 包使用方法,并結合實驗分析了包內不同類的表現。同時對該包在不同 Java 虛擬機上的表現進行了深入地分析。
總結
以上是生活随笔為你收集整理的深入探讨 java.lang.ref 包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Struts2解析FreeMarker模
- 下一篇: Struts2 在页面定义变量 s:se