四、
一種代替最終化的選擇
在前面一節中的示例還存在一種不確定性可能:JVM并不能保證它在最終化隊列中調用對象的終結器的順序。而來自于所有類(應用程序,庫,等等)的終結器都是被同等對待的。因此,一個占有大量內存或一種稀有的本地資源的對象可能受阻于終結化隊列-它們排在那些終結器進度緩慢的對象之后(不一定是惡意;也許由于懶惰的編程所致)。
看完上面達內培訓老師分享的的第一段內容之后大家是不是已經知道今天我們要為大家分享什么內容了那?
為了避免這種類型的不確定性,你可以使用弱參考來代替最終化,例如使用死后鉤子(postmortem
hook)。如果用這種方式,你可以完全控制怎樣優先化本地資源的回收問題,而代替依賴于JVM完成這件事情。下面的示例展示了這一技術:
final
class NativeImage3 extends WeakReference {
private
int nativeImg;//指向本地圖像數據
//它釋放本地圖像;隨后對它的調用將被忽略
private
native void disposeNative();
void
dispose() {
disposeNative();
refList.remove(this);
}
static
private ReferenceQueue refQueue;
static
private List refList;
static
ReferenceQueue referenceQueue() {return refQueue;}
NativeImage3(Image3
img) {
super(img,
refQueue);
refList.add(this);
}
}
public
class Image3 {
private
NativeImage3 nativeImg;
private
Point pos;
private
Dimension dim;
public
void dispose() { nativeImg.dispose(); }
}
Image3與Image2相同。NativeImage3相似于NativeImage2,但是它的最后清理依賴于弱參考而不是最終化。NativeImage3擴展WeakReference,其參考是與之相關聯的Image3實例。請記住,當一個參考對象的參考(此時是WeakReference)成為不可達的時,該參考對象就被添加到與之相關聯的參考隊列上。把nativeImg嵌入到參考對象本身就保證JVM會正確地把所需要的加入到隊列中(見圖6)。再強調一下,NativeImage3不應該成為Image3的一個子類,這是基于前面所述原因。
圖6.把nativeImg嵌入到Reference對象本身
你可以決定是否一參考對象的參考物已經被垃圾收集器以兩種方式回收:顯式地,在參考對象上調用get()方法;隱式地,通過觀察參考對象已經在相關聯的參考隊列中排隊來實現。本示例中只使用了后者。
注意,參考對象僅能被垃圾收集器所發現并且被添加到它們的相關聯的參考隊列-只有它們本身是可達的時候。否則,它們就象任何其它不可達的對象一樣被簡單地回收。這就是為什么你把所有的NativeImage3實例添加到該靜態鏈表(實際上,任何數據結構都會滿足):為了確保它們保持為可達的并且當它們的參考物成為不可達的時被處理。當然,你還必須確保當你釋放它們時(這是通過dispose()方法來實現的)你從該列表中刪除了它們。
當在一個Image3實例上顯式地調用dispose()方法時,在該實例上不會發生隨后的最后清理;正確情況下也是這樣,因為這里不需要任何東西。這個dispose()方法從靜態列表中刪除NativeImage3實例,這樣當它的相應的Image3實例成為不可達的時它就是不可達的。并且,如前所述,不可達的參考對象并不被添加到它們相應的參考隊列。相反,在所有前面的使用了最終化的示例中,可最終化的對象將總是被作最終化考慮-當它們成為不可達的時候,無論你是否已顯式地釋放它們相關聯的本地資源。
JVM將保證,當通過垃圾收集器發現一個Image3實例是不可達的時候,它會把它的相應的NativeImage3實例添加到它的相關聯的參考隊列上去。然后,由你負責把它從隊列中刪除并釋放它的本地資源。這可以通過在一個"清理"線程中,用一個如下的循環來實現:
ReferenceQueue
refQueue =NativeImage3.referenceQueue();
while
(true) {
NativeImage3
nativeImg =(NativeImage3) refQueue.remove();
nativeImg.dispose();
}
這是一個過于簡單的實例。高級開發者能另外根據它們如何需要優先化清理來確保不同參考對象關聯于不同的參考隊列。并且一個單個的"清理"線程可以查詢所有可用的參考隊列和根據要求的優先級來從隊列中刪除對象。另外,你可以選擇展開(spread
out)回收資源,這樣它就會給應用程序帶來更少的危險性。
盡管用這種方式清理資源與使用最終化相比,明顯是更復雜些,但是這也是一種更為有力量和更為靈活的方式,而且可以最小化大量的與使用最終化相關的不確定性。另外,這種方式還十分相似于最終化實際在JVM內實現的方式。對于那些顯式地使用很多本地資源并且需要更多控制的工程,我推薦對它們進行清理時使用這一方法。而只要小心地使用最終化對于大多數另外的工程來說也就足夠了。
注意:本文僅討論了兩種類型的在使用最終化時產生的問題,也就是內存和資源保留問題。最終化和參考類的使用也能帶來很微妙的同步問題。要想詳細了解這一點,可以參考Read
Hans-J.Boehm的《最終化,線程和基于Java技術的內存模型》一文。
五、僅在必要時才使用最終化
本文簡短描述了最終化是怎樣在JVM中實現的。然后給出了有關內存是怎樣不必被可最終化的對象所保留的示例并且概括了這種問題的解決方案。最后,我描述了一個方法-它使用弱參考來代替-這允許你用一種更為靈活和可預測的方式來執行最后清理。