成人午夜激情影院,小视频免费在线观看,国产精品夜夜嗨,欧美日韩精品一区二区在线播放

怎樣處理Java最終化的內(nèi)存保留問題

2010-08-28 10:48:35來源:西部e網(wǎng)作者:

    最終化允許實現(xiàn)Java對象的最后清理;但是,就算不顯式地使用它,它也能延遲資源的回收。在本文中你還會學(xué)習(xí)如何避免類似的內(nèi)存保留問題。

  一、 引言

  最終化是Java編程語言的一個特性:它允許你對垃圾收集器發(fā)現(xiàn)的不可達(dá)的對象進(jìn)行最后清理。典型地用于回收與一對象相關(guān)聯(lián)的本地資源。下面是一個簡單的最終化示例:

public class Image1 {
 //指向本地圖像數(shù)據(jù)
 private int nativeImg;
 private Point pos;
 private Dimension dim;
 //它釋放本地圖像;
 //隨后對它的調(diào)用將被忽略
 private native void disposeNative();
 public void dispose() { disposeNative(); }
 protected void finalize() { dispose(); }
 static private Image1 randomImg;
}


圖1.一個可最終化的對象

  有時,在一個Image1實例變?yōu)椴豢蛇_(dá)的時,Java虛擬機(JVM)將調(diào)用它的finalize()方法來確保含有圖像數(shù)據(jù)(在本例中被整數(shù)nativeImg所指向)的本地資源已經(jīng)被回收。然而,請注意,該finalize()方法,先不管它被JVM進(jìn)行專門對待,是一個任意的方法-它包含任意的代碼。特別地,它能存取任何對象中的任何字段(在本例中指pos和dim)。令人驚訝的是,它還能使該對象再次成為可達(dá)的-比如說通過讓它從一個靜態(tài)字段成為可達(dá)的(如,randomImg=this;)。我根本不推薦使用后面這種編程實踐,但是遺憾的是,Java編程語言允許它。

  下面步驟描述一個可最終化的對象obj的生命周期-也即,這是一個其類中有一個非平凡的(non-trivial)終結(jié)器的對象(見圖1):


圖2.垃圾收集器確定這個obj是不可達(dá)的


  1. 當(dāng)obj被分配時,JVM內(nèi)部地記錄下這個obj是可最終化的(這在典型情況下會減慢現(xiàn)代JVM具有的其它方面的分配路徑)。

  2. 當(dāng)垃圾收集器確定該obj是不可達(dá)的時,它注意到,這個obj是可最終化的(因為它在分配時就被記錄下來)并且把它添加到JVM的最終化隊列上。它還確保從obj可達(dá)的所有對象被保留起來,即使它們從其它對象也許是不可達(dá)的,因為它們可能會被終結(jié)器所存取。圖2展示了Image1的一個實例的情況。

  3. 在后面的時候,JVM的終結(jié)器線程將出隊obj,調(diào)用它的finalize()方法,并且記錄下該obj的終結(jié)器已經(jīng)被調(diào)用。此時,obj被認(rèn)為是被最終化的。

  4. 當(dāng)垃圾收集器再次發(fā)現(xiàn)該obj是不可達(dá)的時,它將連同一切它所可達(dá)的(假定后者是不可達(dá)的)對象回收它的空間。

  注意,垃圾收集器至少需要兩個周期(也許更多)來回收obj并且需要保留在該過程中所有另外的從obj可達(dá)的對象。如果一個程序員不小心,那么這可能會創(chuàng)建暫時的、微妙的和無法預(yù)言的資源保留問題。另外,JVM并不保證它將調(diào)用所有的已分配的可最終化的對象的終結(jié)器;它可能在垃圾收集器發(fā)現(xiàn)它們其中一些是不可達(dá)的之前就已退出。

    二、 在子類化時避免內(nèi)存保留問題

  就算你不顯式地使用它,最終化也可能延期資源的回收。請考慮下列實例:

public class RGBImage1 extends Image1 {
 private byte rgbData[];
}


  RGBImage1擴展Image1并且引入了新字段rgbData(也許還有一些方法,而本示例中卻沒有顯示)。盡管你沒有顯式地在RGBImage1上定義一終結(jié)器,但是,這個類將自然地繼承Image1的finalize()方法,并且所有的RGBImage1實例也將被認(rèn)為是可最終化的。當(dāng)一個RGBImage1實例成為不可達(dá)的,回收可能的很大的rgbData數(shù)組將被延遲直到該實例被終結(jié)(見圖3)。這可能是一個很難發(fā)現(xiàn)的問題,因為該終結(jié)器可能是隱藏在一個很深的類層次上。

  一種避免這個問題的方法是重新安排代碼,這樣它可以使用"包含"來代替"擴展"模式,如下所示:

public class RGBImage2 {
 private Image1 img;
 private byte rgbData[];
 public void dispose() {img.dispose();}
}



圖3.GC將因最終化而只排隊Image1實例


  與RGBImage1相比,RGBImage2包含一個Image1的實例而不是擴展Image1。當(dāng)RGBImage2的一個實例成為不可達(dá)時,垃圾回收器將即時回收它,連同rgbData數(shù)組(假定后者從任何其它地方都是不可達(dá)的),并且在最終化時將只排隊Image1實例(見圖4)。既然類RGBImage2并沒有子類化Image1,那么它就不會從它中繼承任何方法。因此,你可能必須把delegator方法添加到RGBImage1以存取要求的Image1中的方法(dispose()方法就是這樣的一個例子)。

  然而,你不可能總是用上面描述的方式重新安排你的代碼。在這種情況下,作為一個類用戶,你必須做點多余的工作來確保當(dāng)它們被終結(jié)時其實例并不占有多余的空間。下列代碼說明實現(xiàn)方法:

public class RGBImage3 extends Image1 {
 private byte rgbData[];
 public void dispose() {
  super.dispose();
  rgbData = null;
 }
}



圖4.在使用一個RGBImage3實例后調(diào)用dispose()


  RGBImage3與RGBImage1相同,但是添加了dispose()方法-它用來把rgbData字段置為null。你需要顯式地在使用完一個RGBImage3實例之后調(diào)用dispose()以保證rgbData數(shù)組被即時回收(見圖4)。我推薦在極少的場合下顯式地把字段置為null;這里就是其中之一。

    三、保護(hù)用戶免于內(nèi)存保留問題

  前一節(jié)描述了在用使用終結(jié)器的第三方類工作時怎樣避免內(nèi)存保留問題。本節(jié)將描述怎樣創(chuàng)建需要最后清理的類,這樣以來它們的用戶就不會遇到前面所概括的問題。為此,最好的方法是把這樣的類分解為兩個(一個持有需要最后清理的數(shù)據(jù),另一個持有其它一切)并且只在前者上定義一個終結(jié)器。下面的代碼展示了這一技術(shù):

final class NativeImage2 {
 private int nativeImg;//指向本地圖像數(shù)據(jù)
 //它釋放本地圖像;隨后對它的調(diào)用將被忽略
 private native void disposeNative();
 void dispose() { disposeNative(); }
 protected void finalize() { dispose(); }
}
public class Image2 {
 private NativeImage2 nativeImg;
 private Point pos;
 private Dimension dim;
 public void dispose() { nativeImg.dispose(); }
}



圖5.當(dāng)Image2實例成為不可達(dá)時,只有NativeImage2實例將會排隊


  Image2相似于Image1,但是它的nativeImg字段被包含在一個獨立的類NativeImage2中。所有從圖像類到nativeImg的存取必須經(jīng)由一個重定向?qū)印H欢?dāng)一個Image2實例成為不可達(dá)的時候,只有NativeImage2實例將排隊等待最終化;任何其它從Image2實例可達(dá)的都將被提示回收(見圖5)。類NativeImage2被聲明為final,這樣用戶就不可能把它子類化并且重新引入了前一節(jié)所描述的內(nèi)存保留問題。

  一處微妙的地方在于,NativeImage2不應(yīng)該成為一個Image2的內(nèi)部類。內(nèi)部類的實例都有一個到創(chuàng)建它們的外部類的實例的隱含參考。所以,如果NativeImage2是Image2的一個內(nèi)部類,并且一個NativeImage2實例在排隊等待最終化,它應(yīng)該保留相應(yīng)的Image2實例,這恰恰是前面你盡力想避免的。然而,假定NativeImage2類只能從Image2類中進(jìn)行存取。這就是為什么它沒有公共方法的原因(它的dispose()方法,以及類本身都是為包所私有的)。

    四、 一種代替最終化的選擇

  在前面一節(jié)中的示例還存在一種不確定性可能:JVM并不能保證它在最終化隊列中調(diào)用對象的終結(jié)器的順序。而來自于所有類(應(yīng)用程序,庫,等等)的終結(jié)器都是被同等對待的。因此,一個占有大量內(nèi)存或一種稀有的本地資源的對象可能受阻于終結(jié)化隊列-它們排在那些終結(jié)器進(jìn)度緩慢的對象之后(不一定是惡意;也許由于懶惰的編程所致)。

  為了避免這種類型的不確定性,你可以使用弱參考來代替最終化,例如使用死后鉤子(postmortem hook)。如果用這種方式,你可以完全控制怎樣優(yōu)先化本地資源的回收問題,而代替依賴于JVM完成這件事情。下面的示例展示了這一技術(shù):

final class NativeImage3 extends WeakReference<Image3> {
 private int nativeImg;//指向本地圖像數(shù)據(jù)
 //它釋放本地圖像;隨后對它的調(diào)用將被忽略
 private native void disposeNative();
 void dispose() {
  disposeNative();
  refList.remove(this);
 }
 static private ReferenceQueue<Image3> refQueue;
 static private List<NativeImage3> refList;
 static ReferenceQueue<Image3> 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,其參考是與之相關(guān)聯(lián)的Image3實例。請記住,當(dāng)一個參考對象的參考(此時是WeakReference)成為不可達(dá)的時,該參考對象就被添加到與之相關(guān)聯(lián)的參考隊列上。把nativeImg嵌入到參考對象本身就保證JVM會正確地把所需要的加入到隊列中(見圖6)。再強調(diào)一下,NativeImage3不應(yīng)該成為Image3的一個子類,這是基于前面所述原因。


圖6.把nativeImg嵌入到Reference對象本身

  你可以決定是否一參考對象的參考物已經(jīng)被垃圾收集器以兩種方式回收:顯式地,在參考對象上調(diào)用get()方法;隱式地,通過觀察參考對象已經(jīng)在相關(guān)聯(lián)的參考隊列中排隊來實現(xiàn)。本示例中只使用了后者。

  注意,參考對象僅能被垃圾收集器所發(fā)現(xiàn)并且被添加到它們的相關(guān)聯(lián)的參考隊列-只有它們本身是可達(dá)的時候。否則,它們就象任何其它不可達(dá)的對象一樣被簡單地回收。這就是為什么你把所有的NativeImage3實例添加到該靜態(tài)鏈表(實際上,任何數(shù)據(jù)結(jié)構(gòu)都會滿足):為了確保它們保持為可達(dá)的并且當(dāng)它們的參考物成為不可達(dá)的時被處理。當(dāng)然,你還必須確保當(dāng)你釋放它們時(這是通過dispose()方法來實現(xiàn)的)你從該列表中刪除了它們。

  當(dāng)在一個Image3實例上顯式地調(diào)用dispose()方法時,在該實例上不會發(fā)生隨后的最后清理;正確情況下也是這樣,因為這里不需要任何東西。這個dispose()方法從靜態(tài)列表中刪除NativeImage3實例,這樣當(dāng)它的相應(yīng)的Image3實例成為不可達(dá)的時它就是不可達(dá)的。并且,如前所述,不可達(dá)的參考對象并不被添加到它們相應(yīng)的參考隊列。相反,在所有前面的使用了最終化的示例中,可最終化的對象將總是被作最終化考慮-當(dāng)它們成為不可達(dá)的時候,無論你是否已顯式地釋放它們相關(guān)聯(lián)的本地資源。

  JVM將保證,當(dāng)通過垃圾收集器發(fā)現(xiàn)一個Image3實例是不可達(dá)的時候,它會把它的相應(yīng)的NativeImage3實例添加到它的相關(guān)聯(lián)的參考隊列上去。然后,由你負(fù)責(zé)把它從隊列中刪除并釋放它的本地資源。這可以通過在一個"清理"線程中,用一個如下的循環(huán)來實現(xiàn):

ReferenceQueue<Image3> refQueue =NativeImage3.referenceQueue();
while (true) {
 NativeImage3 nativeImg =(NativeImage3) refQueue.remove();
 nativeImg.dispose();
}

  這是一個過于簡單的實例。高級開發(fā)者能另外根據(jù)它們?nèi)绾涡枰獌?yōu)先化清理來確保不同參考對象關(guān)聯(lián)于不同的參考隊列。并且一個單個的"清理"線程可以查詢所有可用的參考隊列和根據(jù)要求的優(yōu)先級來從隊列中刪除對象。另外,你可以選擇展開(spread out)回收資源,這樣它就會給應(yīng)用程序帶來更少的危險性。

  盡管用這種方式清理資源與使用最終化相比,明顯是更復(fù)雜些,但是這也是一種更為有力量和更為靈活的方式,而且可以最小化大量的與使用最終化相關(guān)的不確定性。另外,這種方式還十分相似于最終化實際在JVM內(nèi)實現(xiàn)的方式。對于那些顯式地使用很多本地資源并且需要更多控制的工程,我推薦對它們進(jìn)行清理時使用這一方法。而只要小心地使用最終化對于大多數(shù)另外的工程來說也就足夠了。

  注意:本文僅討論了兩種類型的在使用最終化時產(chǎn)生的問題,也就是內(nèi)存和資源保留問題。最終化和參考類的使用也能帶來很微妙的同步問題。要想詳細(xì)了解這一點,可以參考Read Hans-J.Boehm的《最終化,線程和基于Java技術(shù)的內(nèi)存模型》一文。

  五、僅在必要時才使用最終化

  本文簡短描述了最終化是怎樣在JVM中實現(xiàn)的。然后給出了有關(guān)內(nèi)存是怎樣不必被可最終化的對象所保留的示例并且概括了這種問題的解決方案。最后,我描述了一個方法-它使用弱參考來代替-這允許你用一種更為靈活和可預(yù)測的方式來執(zhí)行最后清理。

  然而,完全依賴于垃圾收集器來識別不可達(dá)的對象以便與它們相關(guān)聯(lián)的本機和潛在的較為缺乏的資源就可以被回收存在一個嚴(yán)重的不足:典型的情況下,內(nèi)存是豐富的,而用一種豐富的資源來保護(hù)一種潛在地缺乏的資源并不是一個好策略。因此,如果你使用一個你知道它有與之相關(guān)聯(lián)的本地資源(例如,一個GUI組件,文件,套接字)的對象,那么在使用完之后,一定要調(diào)用它的dispose()或equivalent方法。這將保證立即回收本地資源并且減小這些資源流失的可能性。通過這種方式,你可以使用本文中所討論的方法來作為補救性的最后清理而不是作為主要的清除機制。你還應(yīng)該盡量限制你的最終化使用-僅在絕對必要時使用之。總之,最終化是一個不確定的和有時無法預(yù)言的過程。你越少地依賴于它,它對JVM和你的應(yīng)用程序就有越小的影響。

關(guān)鍵詞:Java

贊助商鏈接:

主站蜘蛛池模板: 龙井市| 天门市| 保德县| 永昌县| 苍南县| 淮安市| 望江县| 西宁市| 绿春县| 翼城县| 茶陵县| 子长县| 辽宁省| 宜州市| 揭东县| 冕宁县| 名山县| 玛多县| 长汀县| 京山县| 阿瓦提县| 鄂尔多斯市| 曲周县| 子洲县| 舒城县| 霍林郭勒市| 洪雅县| 松阳县| 南康市| 固镇县| 波密县| 班玛县| 红原县| 奉化市| 清苑县| 蓬溪县| 德清县| 西丰县| 株洲县| 阿荣旗| 瑞安市|