Java中的finalize詳解

  http://www.examda.com/Java/zhuanye/20100907/10595533.html


      程序員都瞭解初始化的重要性,但常常會忘記同樣重要的清除工作。畢竟,誰需要清除一個int 呢?但在使用程序庫時,把一個對象用完後就“棄之不顧”的做法並非總是安全的。當然,Java有垃圾回收器來回收無用對象佔據的內存資源。但也有特殊情況:假定你的對象(並非使用 new)獲得了一塊“特殊”的內存區域,由於垃圾回收器只知道釋放那些經由 new分配的內存,所以它不知道該如何釋放該對象的這塊 “特殊”內存。爲了應對這種情況,Java允許你在類中定義一個名爲finalize( )的方法。它的工作原理“應該”是這樣的:一旦垃圾回收器準備好釋放對象佔用的存儲空間,將首先調用其 finalize( )方法,並且在下一次垃圾回收動作發生時,纔會真正回收對象佔用的內存。所以要是你打算用 finalize( ),就能在“垃圾回收時刻”做一些重要的清除工作。

  這裏有一個潛在的編程陷阱,因爲有些程序員(特別是 C++程序員)剛開始可能會誤把finalize( )當作C++中的“析構函數”(C++中銷燬對象必須用到這個函數)。所以有必要明確區分一下:在 C++中,對象一定會被“銷燬”(如果程序中沒有錯誤的話);而 Java 裏的對象卻並非總是被“垃圾回收”的。或者換句話說:

  1. 對象可能不被回收。

  2. 垃圾回收並不等於“析構”。

  牢記這些,你就能遠離困擾。這意味着在你不再需要某個對象之前,如果必須執行某些動作,那麼你得自己去做。Java並未提供“析構函數”或相似的概念,要做類似的清除工作,你必須自己動手創建一個執行清除工作的普通方法。例如,假設某個對象在創建過程中,會將自己繪製到屏幕上。要是你不明確地從屏幕上將其擦除,它可能永遠得不到清除。如果在

  finalize( )里加入某種擦除功能,當“垃圾回收”發生時(不能保證一定會發生),finalize( )得到了調用,圖像就會被擦除。要是“垃圾回收”沒有發生,圖像就會一直保留下來。

  也許你會發現,只要程序沒有瀕臨存儲空間用完的那一刻,對象佔用的空間就總也得不到釋放。如果程序執行結束,並且垃圾回收器一直都沒有釋放你創建的任何對象的存儲空間,則隨着程序的退出,那些資源會全部交還給操作系統。這個策略是恰當的,因爲垃圾回收本身也有開銷,要是不使用它,那就不用支付這部分開銷了。

  finalize( )用途何在?

  此時,你已經明白了不該將finalize( )作爲通用的清除方法。那麼,finalize( )的真正用途是什麼呢?

  這引出了要記住的第三點:

  3.垃圾回收只與內存有關。

  也就是說,垃圾回收器存在的唯一原因是爲了回收程序不再使用的內存。所以對於與垃圾回收有關的任何行爲來說(尤其是finalize( )方法),它們也必須同內存及其回收有關。

  但這是否意味着要是對象中含有其他對象,finalize( )就應該明確釋放那些對象呢?不——無論對象是如何創建的,垃圾回收器都會負責釋放對象佔據的所有內存。這就將對 finalize( )的需求限制到特殊情況之下:你通過某種非“創建對象”的方式爲對象分配了存儲空間。不過,你也看到了,Java中一切皆爲對象,那這種特殊情況是怎麼回事呢?

  看來之所以要有finalize( ),是由於你可能在分配內存時,採用了類似C語言中的做法而非Java中的通常做法。這種情況主要發生在使用“本地方法”的情況下,它是在Java中調用非Java代碼的一種方式。本地方法目前只支持C和C++。但它們可以調用其它語言寫的代碼,所以你實際上可以調用任何代碼。在非Java代碼中,也許會調用類似C的malloc( )函數,用它分配存儲空間,而且除非調用了free( )函數,否則存儲空間將不會得到釋放,從而造成內存泄露。當然,free( )是C和C++中的函數,所以你需要在finalize( )中用本地方法調用它。

  至此,你或許已經明白了不要過多地使用finalize( )的道理了。對,它確實不是進行普通的清除工作的合適場所。那麼,普通的清除工作應該在哪執行呢?

  你必須執行清除

  爲清除一個對象,用戶必須在進行清除的時刻調用執行清除動作的方法。聽起來似乎很簡單,但卻與C++中的“析構函數”的概念稍有牴觸。在C++中,所有對象都會被銷燬,或者說, “應該”被銷燬。如果在C++中創建了一個局部對象(就是在堆棧上創建,Java中可不行),此時的銷燬動作發生在以“右花括號”爲邊界的、此對象作用域的末尾處進行。如果對象是

  用new創建的(類似於Java),那麼當程序員調用C++的delete( )時(Java沒有這個命令),就會調用相應的析構函數。如果程序員忘了,那麼永遠不會調用析構函數,就會出現內存泄露,對象的其他部分也不會得到清除。這種錯誤很難跟蹤,這也是讓 C++程序員轉向 Java的一個主要因素。

  相反,Java不允許創建局部對象,你必須使用new。在Java中,也沒有“delete”來釋放對象,因爲垃圾回收器會幫助你釋放存儲空間。甚至可以膚淺地認爲,正是由於垃圾收集機制的存在,使得 Java 沒有析構函數。然而,隨着學習的深入,你就會明白垃圾回收器的存在並不能完全代替析構函數。(而且你絕對不能直接調用finalize( ),所以這也不是一個恰當的

  途徑。)如果你希望進行除釋放存儲空間之外的清除工作,你還是得明確調用某個恰當的Java方法。這就等同於使用析構函數了,而且沒有它方便。

  記住,無論是“垃圾回收”還是“終結”,都不保證一定會發生。如果Java虛擬機(JVM)並未面臨內存耗盡的情形,它是不會浪費時間在回收垃圾以恢復內存上的。

  終結條件

  通常,你不能指望finalize( ),你必須創建其它的“清除”方法,並且明確地調用它們。看來,finalize( )只能存在於程序員很難用到的一些晦澀用法裏了。不過,finalize( )還有一個有趣的用法,它並不依賴於每次都要對finalize( )進行調用,這就是對象“終結條件”的驗證。

  當你對某個對象不再感興趣,也就是它可以被清除時,這個對象應該處於某種狀態,使它佔用的內存可以被安全地釋放。例如,要是對象代表了一個打開的文件,在對象被回收前程序員應該關閉這個文件。只要對象中存在沒有被適當清除的部分,你的程序就存在很隱晦的錯誤。finalize( )的價值在於可以用來最終發現這種情況,儘管它並不總是會被調用。如果某次

  finalize( )的動作使得bug被發現,那你就可據此找出問題所在——這纔是你真正關心的。

  以下是個簡單的例子,示範了可能的使用方式:

  class Book {

  boolean checkedOut = false;

  Book(boolean checkOut) {

  checkedOut = checkOut;

  }

  void checkIn() {

  checkedOut = false;

  }

  public void finalize() {

  if(checkedOut)

  System.out.println("Error: checked out");

  // Normally, you'll also do this:

  // super.finalized();

  }

  }

  public class TerminationCondition {

  public static void main(String[] args) {

  Book novel = new Book(true);

  // Proper cleanup:

  novel.checkIn();

  // Drop the reference, forget to clean up:

  new Book(true);

  // Force garbage collection & finalization:

  System.gc();

  }

  }

  本例的終結條件是:所有的Book對象在被當作垃圾回收前都應該被簽入(check in)。但在main( )方法中,由於程序員的錯誤,有一本書未被簽入。要是沒有 finalize( )來驗證終結條件,將很難發現這種錯誤。

  注意,System.gc( )用於強制終結動作的進行。即使不這麼做的話,通過重複的執行程序(假設程序將分配大量的存儲空間而導致垃圾回收動作的執行),最終也能找出錯誤的Book對象。

發佈了53 篇原創文章 · 獲贊 32 · 訪問量 83萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章