深入探讨 java.lang.ref 包--转
概述
Java.lang.ref 是 Java 類庫中比較特殊的一個包,它提供了與 Java 垃圾回收器密切相關(guān)的引用類。這些引用類對象可以指向其它對象,但它們不同于一般的引用,因為它們的存在并不防礙 Java 垃圾回收器對它們所指向的對象進行回收。其好處就在于使者可以保持對使用對象的引用,同時 JVM 依然可以在內(nèi)存不夠用的時候?qū)κ褂脤ο筮M行回收。因此這個包在用來實現(xiàn)與緩存相關(guān)的應(yīng)用時特別有用。同時該包也提供了在對象的“可達(dá)”性發(fā)生改變時,進行提醒的機制。本文通過對該包進行由淺入深的介紹與分析,使讀者可以加深對該包的理解,從而更好地利用該包進行開發(fā)。
回頁首
java.lang.ref 包的介紹
我們可以先來看一下 java.lang.ref 這個包的結(jié)構(gòu),如圖 1 所示
圖 1. java.lang.ref 包結(jié)構(gòu)
該包中各類的繼承關(guān)系如圖 2 所示
圖 2. java.lang.ref 包中類的繼承關(guān)系 :
Reference 是一個抽象類,而 SoftReference,WeakReference,PhantomReference 以及 FinalReference 都是繼承它的具體類。
接下來我們來分別介紹和分析強引用以及 java.lang.ref 包下各種虛引用的特性及用法。
回頁首
StrongReference, SoftReference, WeakReference 以及 PhantomReference 的特性及用法
StrongReference:
我們都知道 JVM 中對象是被分配在堆(heap)上的,當(dāng)程序行動中不再有引用指向這個對象時,這個對象就可以被垃圾回收器所回收。這里所說的引用也就是我們一般意義上申明的對象類型的變量(如 String, Object, ArrayList 等),區(qū)別于原始數(shù)據(jù)類型的變量(如 int, short, long 等)也稱為強引用。
在了解虛引用之前,我們一般都是使用強引用來對對象進行引用。如:
清單 1. StrongReference usage
String tag = new String("T");此處的 tag 引用就稱之為強引用。而強引用有以下特征:
- 強引用可以直接訪問目標(biāo)對象。
- 強引用所指向的對象在任何時候都不會被系統(tǒng)回收。
- 強引用可能導(dǎo)致內(nèi)存泄漏。
我們要討論的這三種 Reference 較之于強引用而言都屬于“弱引用”,也就是他們所引用的對象只要沒有強引用,就會根據(jù)條件被 JVM 的垃圾回收器所回收,它們被回收的時機以及用法各不相同。下面分別來進行討論。
SoftReference:
SoftReference 在“弱引用”中屬于最強的引用。SoftReference 所指向的對象,當(dāng)沒有強引用指向它時,會在內(nèi)存中停留一段的時間,垃圾回收器會根據(jù) JVM 內(nèi)存的使用情況(內(nèi)存的緊缺程度)以及 SoftReference 的 get() 方法的調(diào)用情況來決定是否對其進行回收。(后面章節(jié)會用幾個實驗進行闡述)
具體使用一般是通過 SoftReference 的構(gòu)造方法,將需要用弱引用來指向的對象包裝起來。當(dāng)需要使用的時候,調(diào)用 SoftReference 的 get() 方法來獲取。當(dāng)對象未被回收時 SoftReference 的 get() 方法會返回該對象的強引用。如下:
清單 2. SoftReference usage
SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10)); System.out.println(bean.get());// “name:10”| 強引用 | 直接調(diào)用 | 不回收 | 可能 |
| 軟引用 | 視內(nèi)存情況回收 | 不可能 | |
| 弱引用 | 通過 get() 方法 | 永遠(yuǎn)回收 | 不可能 |
| 虛引用 | 無法取得 | 不回收 | 可能 |
?
| 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();
在虛擬機的實現(xiàn)過程中,實際采用了 FinalReference 類對其進行引用。而 Finalizer,除了作為一個實現(xiàn)類外,更是在虛擬機中實現(xiàn)一個 FinalizerThread,以使虛擬機能夠在所有的強引用被解除后實現(xiàn)內(nèi)存清理。
讓我們來看看 Finalizer 是如何工作的。首先,通過聲明 FinalizerThread,并將該線程實例化,設(shè)置為守護線程后,加入系統(tǒng)線程中去。
清單 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 的過程中,當(dāng)一個強引用被釋放,由系統(tǒng)垃圾收集器標(biāo)記后的對象,會被加入 Finalizer 對象中的 ReferenceQueue 中去,并調(diào)用 Finalizer.runFinalizer() 來執(zhí)行對象的 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 實現(xiàn)所造成的變量未被回收的假象 */ finalizee = null; } } catch (Throwable x) { } super.clear(); }注意,標(biāo)記處所調(diào)用的 invokeFinalizeMethod 為 native 方法,由于 finalize 方法在 Object 類中被聲明為 protected,這里必須采用 native 方法才能調(diào)用。隨后通過將本地強引用設(shè)置為空,以便使垃圾回收器清理內(nèi)存。
可以看到,通過這樣的方法,Java 將四種引用對象類型:軟引用 (SoftReference),弱引用 (WeakReference),強引用 (FinalReference),虛引用 (PhantomReference) 平等地對待,并在垃圾回收器中進行統(tǒng)一調(diào)度和管理。
回頁首
不同 Java 虛擬機上的表現(xiàn)與分析
讓我們來回顧一下四種引用類型的表現(xiàn)以及在垃圾回收器回收清理內(nèi)存時的表現(xiàn) .
這里比較兩個比較典型的 JVM 環(huán)境,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) { // 創(chuàng)建三種不同的引用類型所需對象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()); } // 開始執(zhí)行垃圾回收System.gc(); System.runFinalization(); // 檢查隊列,是否已經(jīng)被加入隊列,是否還能取回對象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()); } // 對于虛引用對象,在經(jīng)過多次 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")); } }通過執(zhí)行 RefMainThread, 我們可以清晰地根據(jù)打印結(jié)果看到對象在內(nèi)存中被加入隊列 , 以及調(diào)用 finalize 方法的順序及過程 .
為了測試不同的 JVM 環(huán)境并消除其他因素的印象 , 本例采用的背景環(huán)境均為 Windows2003 下的 32bit JVM.
首先采用了環(huán)境為 Oracle Java SE 6 update 23 進行測試 , 結(jié)果如下 :
清單 15. Oracle Java SE 6 update 23 下測試結(jié)果
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可以看到 , 當(dāng)運行了系統(tǒng)回收后 , 虛引用與弱引用被回收 , 由于內(nèi)存并不吃緊 , 軟引用依然保持原樣 . 弱引用立即被加入了隊列 , 而虛引用則在循環(huán)兩次的手動調(diào)用 GC 后被加入了隊列 . 其次 , 采用的環(huán)境是 IBM JDK 6, 結(jié)果如下 :
清單 16. IBM JDK 6 下測試結(jié)果
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 ............程序運行到這里進入了無限循環(huán),必須手動終止。比對上下兩份結(jié)果可以看到,當(dāng)多次運行系統(tǒng)垃圾回收后,IBM JVM 將軟引用一并加入了回收隊列中,并運行了其 finalize 方法。另外,即使經(jīng)過很多次系統(tǒng)垃圾回收,虛引用也沒有被加入到隊列中去。不知道這是不是 IBM JVM 的一個小小的 BUG 所在。
結(jié)論
- SoftReference 中 Oracle JVM 的表現(xiàn)滿足規(guī)范,只當(dāng)內(nèi)存不足時才進行回收。而 IBM JVM 的策略則更為積極,在內(nèi)存尚且充足的情況下也進行了回收,值得注意。
- PhantomReference 中 Oracle JVM 的表現(xiàn)滿足規(guī)范,執(zhí)行 finalize 后若干次 GC 就被添加到了 Queue 中。而 IBM JVM 則始終沒有被添加到 Queue 中導(dǎo)致了死循環(huán)。所以在使用 PhantomReference 時出現(xiàn)類似的情況時,可以考慮是否是因為使用了不同 JVM 所導(dǎo)致。
回頁首
小結(jié)
本文深入地介紹了 java.lang.ref 包使用方法,并結(jié)合實驗分析了包內(nèi)不同類的表現(xiàn)。同時對該包在不同 Java 虛擬機上的表現(xiàn)進行了深入地分析。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-langref/
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/4049434.html
總結(jié)
以上是生活随笔為你收集整理的深入探讨 java.lang.ref 包--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 Java 2 运行时安全模型的线程
- 下一篇: Java 异常处理的误区和经验总结--转