Java 2 平臺引入了 java.lang.ref 包,其中包括的類可以讓您引用對象,而不將它們留在內存中。這些類還提供了與垃圾收集器(garbage collector)之間有限的交互。Peter Haggar 在本文中分析了 SoftReference、WeakReference 和 PhantomReference 類的功能和行為,并就這些類的使用給出了一些編程風格上的建議。
當在 Java 2 平臺中首次引入 java.lang.ref 包(其中包含 SoftReference、WeakReference 和 PhantomReference 類)時,它的實用性顯然被過分夸大了。它包含的類可能是有用的,但這些類具有的某些局限性會使它們顯得不是很有吸引力,而且其應用程序也將特別局限于解決一類特定的問題。 垃圾收集概述 引用類的主要功能就是能夠引用仍可以被垃圾收集器回收的對象。在引入引用類之前,我們只能使用強引用(strong reference)。舉例來說,下面一行代碼顯示的就是強引用 obj: Object obj = new Object(); obj 這個引用將引用堆中存儲的一個對象。只要 obj 引用還存在,垃圾收集器就永遠不會釋放用來容納該對象的存儲空間。 當 obj 超出范圍或被顯式地指定為 null 時,垃圾收集器就認為沒有對這個對象的其它引用,也就可以收集它了。然而您還需要注意一個重要的細節:僅憑對象可以被收集并不意味著垃圾收集器的一次指定運行就能夠回收它。由于各種垃圾收集算法有所不同,某些算法會更頻繁地分析生存期較短的對象,而不是較老、生存期較長的對象。因此,一個可供收集的對象可能永遠也不會被回收。如果程序在垃圾收集器釋放對象之前結束,這種情況就可能會出現。因此,概括地說,您永遠無法保證可供收集的對象總是會被垃圾收集器收集。 這些信息對于您分析引用類是很重要的。由于垃圾收集有著特定的性質,所以引用類實際上可能沒有您原來想像的那么有用,盡管如此,它們對于特定問題來說還是很有用的類。軟引用(soft reference)、弱引用(weak reference)和虛引用(phantom reference)對象提供了三種不同的方式來在不妨礙收集的情況下引用堆對象。每種引用對象都有不同的行為,而且它們與垃圾收集器之間的交互也有所不同。此外,這幾個新的引用類都表現出比典型的強引用“更弱”的引用形式。而且,內存中的一個對象可以被多個引用(可以是強引用、軟引用、弱引用或虛引用)引用。在進一步往下討論之前,讓我們來看看一些術語: 強可及對象(strongly reachable):可以通過強引用訪問的對象。 軟可及對象(softly reachable):不是強可及對象,并且能夠通過軟引用訪問的對象。 弱可及對象(weakly reachable):不是強可及對象也不是軟可及對象,并且能夠通過弱引用訪問的對象。 虛可及對象(phantomly reachable):不是強可及對象、軟可及對象,也不是弱可及對象,已經結束的,可以通過虛引用訪問的對象。 清除:將引用對象的 referent 域設置為 null,并將引用類在堆中引用的對象聲明為可結束的。 SoftReference 類 SoftReference 類的一個典型用途就是用于內存敏感的高速緩存。SoftReference 的原理是:在保持對對象的引用時保證在 JVM 報告內存不足情況之前將清除所有的軟引用。關鍵之處在于,垃圾收集器在運行時可能會(也可能不會)釋放軟可及對象。對象是否被釋放取決于垃圾收集器的算法以及垃圾收集器運行時可用的內存數量。 WeakReference 類 WeakReference 類的一個典型用途就是規范化映射(canonicalized mapping)。另外,對于那些生存期相對較長而且重新創建的開銷也不高的對象來說,弱引用也比較有用。關鍵之處在于,垃圾收集器運行時如果碰到了弱可及對象,將釋放 WeakReference 引用的對象。然而,請注意,垃圾收集器可能要運行多次才能找到并釋放弱可及對象。 PhantomReference 類 PhantomReference 類只能用于跟蹤對被引用對象即將進行的收集。同樣,它還能用于執行 pre-mortem 清除操作。PhantomReference 必須與 ReferenceQueue 類一起使用。需要 ReferenceQueue 是因為它能夠充當通知機制。當垃圾收集器確定了某個對象是虛可及對象時,PhantomReference 對象就被放在它的 ReferenceQueue 上。將 PhantomReference 對象放在 ReferenceQueue 上也就是一個通知,表明 PhantomReference 對象引用的對象已經結束,可供收集了。這使您能夠剛好在對象占用的內存被回收之前采取行動。 垃圾收集器和引用交互 垃圾收集器每次運行時都可以隨意地釋放不再是強可及的對象占用的內存。如果垃圾收集器發現了軟可及對象,就會出現下列情況: SoftReference 對象的 referent 域被設置為 null,從而使該對象不再引用 heap 對象。 SoftReference 引用過的 heap 對象被聲明為 finalizable。 當 heap 對象的 finalize() 方法被運行而且該對象占用的內存被釋放,SoftReference 對象就被添加到它的 ReferenceQueue(如果后者存在的話)。 如果垃圾收集器發現了弱可及對象,就會出現下列情況: WeakReference 對象的 referent 域被設置為 null,從而使該對象不再引用 heap 對象。 WeakReference 引用過的 heap 對象被聲明為 finalizable。 當 heap 對象的 finalize() 方法被運行而且該對象占用的內存被釋放時,WeakReference 對象就被添加到它的 ReferenceQueue(如果后者存在的話)。 如果垃圾收集器發現了虛可及對象,就會出現下列情況: PhantomReference 引用過的 heap 對象被聲明為 finalizable。 與軟引用和弱引用有所不同,PhantomReference 在堆對象被釋放之前就被添加到它的 ReferenceQueue。(請記住,所有的 PhantomReference 對象都必須用經過關聯的 ReferenceQueue 來創建。)這使您能夠在堆對象被回收之前采取行動。 請考慮清單 1 中的代碼。 清單 1. 使用 WeakReference 及 ReferenceQueue 的示例代碼 //Create a strong reference to an object MyObject obj = new MyObject(); //1 //Create a reference queue ReferenceQueue rq = new ReferenceQueue(); //2 //Create a weakReference to obj and associate our reference queue WeakReference wr = new WeakReference(obj, rq); //3 行 //1 創建 MyObject 對象,而行 //2 則創建 ReferenceQueue 對象。行 //3 創建引用其引用對象 MyObject 的 WeakReference 對象,還創建它的 ReferenceQueue。請注意,每個對象引用(obj、rq 及 wr)都是強引用。要利用這些引用類,您必須取消對 MyObject 對象的強引用,方法是將 obj 設置為 null。前面說過,如果不這樣做,對象 MyObject 永遠都不會被回收,引用類的任何優點都會被削弱。 每個引用類都有一個 get() 方法,而 ReferenceQueue 類有一個 poll() 方法。get() 方法返回對被引用對象的引用。在 PhantomReference 上調用 get() 總是會返回 null。這是因為 PhantomReference 只用于跟蹤收集。poll() 方法返回已被添加到隊列中的引用對象,如果隊列中沒有任何對象,它就返回 null。因此,執行清單 1 之后再調用 get() 和 poll() 的結果可能是: wr.get(); //returns reference to MyObject rq.poll(); //returns null 現在我們假定垃圾收集器開始運行。由于 MyObject 對象沒有被釋放,所以 get() 和 poll() 方法將返回同樣的值;obj 仍然保持對該對象進行強引用。實際上,對象布局還是沒有改變,和圖 1 所示的差不多。然而,請考慮下面的代碼: obj = null; System.gc(); //run the collector 現在,調用 get() 和 poll() 將產生與前面不同的結果: wr.get(); //returns null rq.poll(); //returns a reference to the WeakReference object 這種情況表明,MyObject 對象(對它的引用原來是由 WeakReference 對象進行的)不再可用。這意味著垃圾收集器釋放了 MyObject 占用的內存,從而使 WeakReference 對象可以被放在它的 ReferenceQueue 上。這樣,您就可以知道當 WeakReference 或 SoftReference 類的 get() 方法返回 null 時,就有一個對象被聲明為 finalizable,而且可能(不過不一定)被收集。只有當 heap 對象完全結束而且其內存被回收后,WeakReference 或 SoftReference 才會被放到與其關聯的 ReferenceQueue 上。清單 2 顯示了一個完整的可運行程序,它展示了這些原理中的一部分。這段代碼本身就頗具說明性,它含有很多注釋和打印語句,可以幫助您理解。 清單 2. 展示引用類原理的完整程序 import java.lang.ref.*; class MyObject { protected void finalize() throws Throwable { System.out.println("In finalize method for this object: " + this); } } class ReferenceUsage { public static void main(String args[]) { hold(); release(); } public static void hold() { System.out.println("Example of incorrectly holding a strong " + "reference"); //Create an object MyObject obj = new MyObject(); System.out.println("object is " + obj); //Create a reference queue ReferenceQueue rq = new ReferenceQueue(); //Create a weakReference to obj and associate our reference queue WeakReference wr = new WeakReference(obj, rq); System.out.println("The weak reference is " + wr); //Check to see if it´s on the ref queue yet System.out.println("Polling the reference queue returns " + rq.poll()); System.out.println("Getting the referent from the " + "weak reference returns " + wr.get()); System.out.println("Calling GC"); System.gc(); System.out.println("Polling the reference queue returns " + rq.poll()); System.out.println("Getting the referent from the " + "weak reference returns " + wr.get()); } public static void release() { System.out.println(""); System.out.println("Example of correctly releasing a strong " + "reference"); //Create an object MyObject obj = new MyObject(); System.out.println("object is " + obj); //Create a reference queue ReferenceQueue rq = new ReferenceQueue(); //Create a weakReference to obj and associate our reference queue WeakReference wr = new WeakReference(obj, rq); System.out.println("The weak reference is " + wr); //Check to see if it´s on the ref queue yet System.out.println("Polling the reference queue returns " + rq.poll()); System.out.println("Getting the referent from the " + "weak reference returns " + wr.get()); System.out.println("Set the obj reference to null and call GC"); obj = null; System.gc(); System.out.println("Polling the reference queue returns " + rq.poll()); System.out.println("Getting the referent from the " + "weak reference returns " + wr.get()); } } 用途和風格 這些類背后的原理就是避免在應用程序執行期間將對象留在內存中。相反,您以軟引用、弱引用或虛引用的方式引用對象,這樣垃圾收集器就能夠隨意地釋放對象。當您希望盡可能減小應用程序在其生命周期中使用的堆內存大小時,這種用途就很有好處。您必須記住,要使用這些類,您就不能保留對對象的強引用。如果您這么做了,那就會浪費這些類所提供的任何好處。 另外,您必須使用正確的編程風格以檢查收集器在使用對象之前是否已經回收了它,如果已經回收了,您首先必須重新創建該對象。這個過程可以用不同的編程風格來完成。選擇錯誤的風格會導致出問題。請考慮清單 3 中從 WeakReference 檢索被引用對象的代碼風格: 清單 3. 檢索被引用對象的風格 obj = wr.get(); if (obj == null) { wr = new WeakReference(recreateIt()); //1 obj = wr.get(); //2 } //code that works with obj 研究了這段代碼之后,請看看清單 4 中從 WeakReference 檢索被引用對象的另一種代碼風格: 清單 4. 檢索被引用對象的另一種風格 obj = wr.get(); if (obj == null) { obj = recreateIt(); //1 wr = new WeakReference(obj); //2 } //code that works with obj 請比較這兩種風格,看看您能否確定哪種風格一定可行,哪一種不一定可行。清單 3 中體現出的風格不一定在所有情況下都可行,但清單 4 的風格就可以。清單 3 中的風格不夠好的原因在于,if 塊的主體結束之后 obj 不一定是非空值。請考慮一下,如果垃圾收集器在清單 3 的行 //1 之后但在行 //2 執行之前運行會怎樣。recreateIt() 方法將重新創建該對象,但它會被 WeakReference 引用,而不是強引用。因此,如果收集器在行 //2 在重新創建的對象上施加一個強引用之前運行,對象就會丟失,wr.get() 則返回 null。 清單 4 不會出現這種問題,因為行 //1 重新創建了對象并為其指定了一個強引用。因此,如果垃圾收集器在該行之后(但在行 //2 之前)運行,該對象就不會被回收。然后,行 //2 將創建對 obj 的 WeakReference。在使用這個 if 塊之后的 obj 之后,您應該將 obj 設置為 null,從而讓垃圾收集器能夠回收這個對象以充分利用弱引用。清單 5 顯示了一個完整的程序,它將展示剛才我們描述的風格之間的差異。(要運行該程序,其運行目錄中必須有一個“temp.fil”文件。 清單 5. 展示正確的和不正確的編程風格的完整程序。 import java.io.*; import java.lang.ref.*; class ReferenceIdiom { public static void main(String args[]) throws FileNotFoundException { broken(); correct(); } public static FileReader recreateIt() throws FileNotFoundException { return new FileReader("temp.fil"); } public static void broken() throws FileNotFoundException { System.out.println("Executing method broken"); FileReader obj = recreateIt(); WeakReference wr = new WeakReference(obj); System.out.println("wr refers to object " + wr.get()); System.out.println("Now, clear the reference and run GC"); //Clear the strong reference, then run GC to collect obj. obj = null; System.gc(); System.out.println("wr refers to object " + wr.get()); //Now see if obj was collected and recreate it if it was. obj = (FileReader)wr.get(); if (obj == null) { System.out.println("Now, recreate the object and wrap it in a WeakReference"); wr = new WeakReference(recreateIt()); System.gc(); //FileReader object is NOT pinned...there is no //strong reference to it. Therefore, the next //line can return null. obj = (FileReader)wr.get(); } System.out.println("wr refers to object " + wr.get()); } public static void correct() throws FileNotFoundException { System.out.println(""); System.out.println("Executing method correct"); FileReader obj = recreateIt(); WeakReference wr = new WeakReference(obj); System.out.println("wr refers to object " + wr.get()); System.out.println("Now, clear the reference and run GC"); //Clear the strong reference, then run GC to collect obj obj = null; System.gc(); System.out.println("wr refers to object " + wr.get()); //Now see if obj was collected and recreate it if it was. obj = (FileReader)wr.get(); if (obj == null) { System.out.println("Now, recreate the object and wrap it in a WeakReference"); obj = recreateIt(); System.gc(); //FileReader is pinned, this will not affect //anything. wr = new WeakReference(obj); } System.out.println("wr refers to object " + wr.get()); } } 總結 如果使用得當,引用類還是很有用的。然而,由于它們所依賴的垃圾收集器行為有時候無法預知,所以其實用性就會受到影響。能否有效地使用它們還取決于是否應用了正確的編程風格;關鍵在于您要理解這些類是如何實現的以及如何對它們進行編程。 ================================================================================= Java 對象的狀態有: * 已創建(created) * 強可達(strong reachable) * 不可見(invisible) * 不可達(unreachable) * 已收集(collected) * 終化(finalized) * 已回收(deallocated) Java對象生命周期的狀態轉換: {image:img=objectstatus.jpg|width=400} 引用對象 三種新的引用類型: * 軟引用(soft reference) * 弱引用(weak reference) * 幻引用(phantom reference) 強可達(Strong Reachable) 定義: ~An object is strong reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strong reachable by the thread that created it.~ 處于強可達狀態的對象, 在任何情況下都不會被回收掉. 軟可達(Softly Reachable) 定義:~An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.~ 含義是:當對象不處于強可達狀態, 并且可以通過軟引用進行訪問時, 即處于軟可達狀態. 當程序申請內存的時候, 垃圾收集器會判斷是否開始回收處于軟可達狀態的對象, 如果決定回收某個對象, 那么垃圾收集器會清除所有指向該對象的軟引用, 如果任何處于其它軟可達狀態的對象可以通過強引用訪問該對象, 那么指向這些對象的軟引用也會被清除掉. 垃圾收集器在決定哪些軟可達狀態的對象被收集時, 采用"最久未被使用"原則, 或稱"最不常使用"原則. 垃圾收集器也保證在OutOfMemeryError產生以前, 所有的軟引用都被清除. * 產生和使用一個軟引用 // createSoftReference sr = new SoftReference(new SomeObject());// getSomeObject o = (SomeObject) sf.get();// create in a reference queue;ReferenceQueue queue = new ReferenceQueue();SoftReference sr = new SoftReference(new SomeObject(), queue); 弱可達(Weakly Reachable) 定義:~An Object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference.~ 垃圾收集器會一次清除所有弱引用. 幻可達(Phantomly Reachable) 定義:~An object is phantomly reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.~ 幻引用不能直接創建. 必須通過向引用隊列等級的途徑來創建: ReferenceQueue queue = new ReferenceQueue();PhantomReference pr = new PhantomReference (new SomeObject(), queue); 你不可能從幻引用再次得到對象, pr.get()永遠返回null. 另外, 必須調用Reference.clear()手工清除幻引用. All About ReferenceObjects No InterWiki reference defined in properties for Wiki called '[http'!)] Reference Objects No InterWiki reference defined in properties for Wiki called '[http'!)] Reference Objects and Garbage Collection No InterWiki reference defined in properties for Wiki called '[http'!)] \[Jike Thread\?Soft, Weak, and Phantom References|http://www-124.ibm.com/pipermail/jikesrvm-core/2003-May/000365.html] |
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
25 | 26 | 27 | 28 | 29 | 30 | 31 | |||
1 | 2 | 3 | 4 | 5 | 6 | 7 | |||
8 | 9 | 10 | 11 | 12 | 13 | 14 | |||
15 | 16 | 17 | 18 | 19 | 20 | 21 | |||
22 | 23 | 24 | 25 | 26 | 27 | 28 | |||
29 | 30 | 1 | 2 | 3 | 4 | 5 |
常用鏈接
留言簿(3)
隨筆檔案
文章分類
- CoreJava(23)
- DB(2)
- Eclipse的相關(18)
- File Version Control(1)
- JAVA2一些基本語法(1)
- JS(1)
- Maven2(1)
- SWT(2)
- Web Test Framework(1)
- WebService(5)
- XML(1)
文章檔案
- 2013年4月 (1)
- 2012年7月 (1)
- 2012年6月 (2)
- 2012年4月 (1)
- 2011年12月 (2)
- 2011年8月 (2)
- 2011年5月 (6)
- 2011年4月 (1)
- 2011年1月 (1)
- 2010年12月 (2)
- 2010年11月 (1)
- 2010年10月 (2)
- 2010年9月 (2)
- 2010年8月 (3)
- 2009年12月 (3)
- 2009年11月 (1)
- 2009年8月 (1)
- 2009年6月 (1)
- 2009年3月 (3)
- 2008年10月 (4)
- 2008年8月 (5)
- 2008年7月 (1)
- 2008年5月 (3)
- 2008年3月 (3)
- 2007年10月 (1)
- 2007年5月 (2)
- 2006年5月 (3)
相冊
搜索
最新評論

- 1.?re: Java Path[未登錄]
- Very helpful, 3ks a lot
- --coder
- 2.?re: MyEclipse + AXIS2
- 評論內容較長,點擊標題查看
- --wangqf
- 3.?re: JVM 內存初學 (堆(heap)、棧(stack)和方法區(method) )
- 寫得挺好的,容易理解。
- --陳同兵
- 4.?re: 獲得當前運行jar包存放路徑的方法
- 評論內容較長,點擊標題查看
- --mu
- 5.?re: MyEclipse + AXIS2
-
從tomcat中的axis2 web應用WEB-INF/lib目錄中加載所有jar包到項目的classpath中。
能不能具體說說怎么做?我將jar文件全部拷貝到項目的lib里……不行 - --zmaike