標籤:
程 序員都瞭解初始化的重要性,但常常會忘記同樣重要的清除工作。畢竟,誰需要清除一個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對象。
引用:http://www.java3z.com/cwbwebhome/article/article8/83463.html?id=4388
java finalize方法