java.lang.ref 包(其中包含 SoftReference、WeakReference 和 PhantomReference 類)時,它的實用性顯然被過分夸大了。它包含的類可能是有用的,但這些類具有的某些局限性會使它們顯得不是很有吸引力,而且其應用程序也將特別局限于解決一類特定的問題。垃圾收集概述
引用類的主要功能就是能夠引用仍可以被垃圾收集器回收的對象。在引入引用類之前,我們只能使用強引用(strong reference)。舉例來說,下面一行代碼顯示的就是強引用 obj:
|
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 說明了這段代碼的執行情況。
清單 1. 使用 WeakReference 及 ReferenceQueue 的示例代碼
|
圖 1. 執行了清單 1 中行 //1、//2 和 //3 的代碼之后的對象布局

圖 1 顯示了每行代碼執行后各對象的狀態。行 //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() 的結果可能是:
|
現在我們假定垃圾收集器開始運行。由于 MyObject 對象沒有被釋放,所以 get() 和 poll() 方法將返回同樣的值;obj 仍然保持對該對象進行強引用。實際上,對象布局還是沒有改變,和圖 1 所示的差不多。然而,請考慮下面的代碼:
|
在這段代碼執行后,對象布局就如圖 2 所示:
圖 2. obj = null; 和垃圾收集器運行后的對象布局

現在,調用 get() 和 poll() 將產生與前面不同的結果:
|
這種情況表明,MyObject 對象(對它的引用原來是由 WeakReference 對象進行的)不再可用。這意味著垃圾收集器釋放了 MyObject 占用的內存,從而使 WeakReference 對象可以被放在它的 ReferenceQueue 上。這樣,您就可以知道當 WeakReference 或 SoftReference 類的 get() 方法返回 null 時,就有一個對象被聲明為 finalizable,而且可能(不過不一定)被收集。只有當 heap 對象完全結束而且其內存被回收后,WeakReference 或 SoftReference 才會被放到與其關聯的 ReferenceQueue 上。清單 2 顯示了一個完整的可運行程序,它展示了這些原理中的一部分。這段代碼本身就頗具說明性,它含有很多注釋和打印語句,可以幫助您理解。
清單 2. 展示引用類原理的完整程序
|
用途和風格
這些類背后的原理就是避免在應用程序執行期間將對象留在內存中。相反,您以軟引用、弱引用或虛引用的方式引用對象,這樣垃圾收集器就能夠隨意地釋放對象。當您希望盡可能減小應用程序在其生命周期中使用的堆內存大小時,這種用途就很有好處。您必須記住,要使用這些類,您就不能保留對對象的強引用。如果您這么做了,那就會浪費這些類所提供的任何好處。
另外,您必須使用正確的編程風格以檢查收集器在使用對象之前是否已經回收了它,如果已經回收了,您首先必須重新創建該對象。這個過程可以用不同的編程風格來完成。選擇錯誤的風格會導致出問題。請考慮清單 3 中從 WeakReference 檢索被引用對象的代碼風格:
清單 3. 檢索被引用對象的風格
|
研究了這段代碼之后,請看看清單 4 中從 WeakReference 檢索被引用對象的另一種代碼風格:
清單 4. 檢索被引用對象的另一種風格
|
請比較這兩種風格,看看您能否確定哪種風格一定可行,哪一種不一定可行。清單 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. 展示正確的和不正確的編程風格的完整程序。
|
總結
如果使用得當,引用類還是很有用的。然而,由于它們所依賴的垃圾收集器行為有時候無法預知,所以其實用性就會受到影響。能否有效地使用它們還取決于是否應用了正確的編程風格;關鍵在于您要理解這些類是如何實現的以及如何對它們進行編程。