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

怎樣處理Java最終化的內存保留問題

2010-08-28 10:48:35來源:西部e網作者:

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

  一、 引言

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

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


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

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

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


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


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

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

  3. 在后面的時候,JVM的終結器線程將出隊obj,調用它的finalize()方法,并且記錄下該obj的終結器已經被調用。此時,obj被認為是被最終化的。

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

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

    二、 在子類化時避免內存保留問題

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

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


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

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

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



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


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

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

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



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


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

    三、保護用戶免于內存保留問題

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

final class NativeImage2 {
 private int nativeImg;//指向本地圖像數據
 //它釋放本地圖像;隨后對它的調用將被忽略
 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.當Image2實例成為不可達時,只有NativeImage2實例將會排隊


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

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

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

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

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

final class NativeImage3 extends WeakReference<Image3> {
 private int nativeImg;//指向本地圖像數據
 //它釋放本地圖像;隨后對它的調用將被忽略
 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,其參考是與之相關聯的Image3實例。請記住,當一個參考對象的參考(此時是WeakReference)成為不可達的時,該參考對象就被添加到與之相關聯的參考隊列上。把nativeImg嵌入到參考對象本身就保證JVM會正確地把所需要的加入到隊列中(見圖6)。再強調一下,NativeImage3不應該成為Image3的一個子類,這是基于前面所述原因。


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

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

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

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

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

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

  這是一個過于簡單的實例。高級開發者能另外根據它們如何需要優先化清理來確保不同參考對象關聯于不同的參考隊列。并且一個單個的"清理"線程可以查詢所有可用的參考隊列和根據要求的優先級來從隊列中刪除對象。另外,你可以選擇展開(spread out)回收資源,這樣它就會給應用程序帶來更少的危險性。

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

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

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

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

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

關鍵詞:Java

贊助商鏈接:

主站蜘蛛池模板: 玉山县| 汽车| 博罗县| 德州市| 拉萨市| 青州市| 通化市| 额尔古纳市| 洛阳市| 泰宁县| 定远县| 德格县| 易门县| 鄂尔多斯市| 平南县| 延吉市| 白城市| 阳山县| 咸丰县| 全椒县| 宝山区| 淮南市| 东至县| 罗定市| 左贡县| 蒙阴县| 焉耆| 全椒县| 偃师市| 江门市| 宿州市| 台东县| 定安县| 阳西县| 沾化县| 武冈市| 仙桃市| 东阿县| 鄂托克前旗| 安西县| 德阳市|