深入探讨 java.lang.ref 包--转
概述
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() 方法 | 永遠回收 | 不可能 |
| 虛引用 | 無法取得 | 不回收 | 可能 |
?
| 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 |
?
回頁首
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 虛擬機上的表現進行了深入地分析。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-langref/
轉載于:https://www.cnblogs.com/davidwang456/p/4049434.html
總結
以上是生活随笔為你收集整理的深入探讨 java.lang.ref 包--转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 Java 2 运行时安全模型的线程
- 下一篇: Java 异常处理的误区和经验总结--转