2008-08-26 26 views
27

在閱讀this question後,我想起了我什麼時候被教授Java並被告知永遠不會調用finalize()或運行垃圾回收器,因爲「這是一個你不需要擔心的大黑盒子」。有人可以將這個原因歸結爲幾句話嗎?我確信我可以閱讀Sun在這個問題上的技術報告,但我認爲一個很好,簡短,簡單的答案會滿足我的好奇心。爲什麼你不顯式調用finalize()或啓動垃圾回收器?

回答

42

簡短回答:Java垃圾回收是一個非常精細的工具。 System.gc()是一個大錘。

Java的堆被分成不同的世代,每個世代都是使用不同的策略收集的。如果您將剖析器附加到健康的應用程序中,您會發現它很少需要運行最昂貴的集合,因爲大多數對象都被年輕一代中更快的複製收集器所捕獲。

直接調用System.gc(),雖然在技術上不能保證做任何事情,但實際上會觸發一個昂貴的,停止世界的完整堆集合。這是幾乎總是做錯事。你認爲你正在節省資源,但實際上你沒有理由浪費它們,迫使Java重新檢查所有活動對象「以防萬一」。

如果您在關鍵時刻出現GC暫停問題,最好將JVM配置爲使用併發標記/清除收集器,該收集器專門用於最大限度地減少暫停的時間,而不是試圖將大錘拿到這個問題,只是進一步打破。

自己所想的太陽文件是在這裏:Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning

(你可能不知道的另一件事情:實現你的目標上的finalize()方法使得垃圾回收慢首先,它會採取 GC。運行以收集對象:一個運行finalize(),另一個運行finalize()以確保該對象在finalization過程中不會被複活;其次,帶有finalize()方法的對象必須被GC視爲特例,因爲它們必須是他們不能僅僅被扔掉。)

0

GC在什麼時候正確完成事情做了很多優化。

所以,除非您熟悉GC的實際工作方式以及它如何標記世代,否則手動調用finalize或啓動GC'ing可能會損害性能而不是幫助。

1

假設終結器與它們的.NET同名相似,那麼只有當你有可能泄漏的文件句柄之類的資源時才需要調用它們。大多數時候你的對象沒有這些引用,所以他們不需要被調用。

試圖收集垃圾是不好的,因爲它不是真的垃圾。您在創建對象時已告知VM分配一些內存,並且垃圾回收器隱藏了有關這些對象的信息。 GC在內部對內存分配進行優化。當你手動嘗試收集垃圾時,你不知道GC想要保留什麼並擺脫掉,你只是強迫它的手。結果你搞砸了內部計算。

如果您對GC內部持有的內容有更多瞭解,那麼您可能可以做出更明智的決定,但是之後您錯過了GC的好處。

4

不要打擾終結者。

切換到增量垃圾回收。

如果您想幫助垃圾收集器,請將對不再需要的對象的引用歸零。較少的路徑=更明確的垃圾。

不要忘記(非靜態)內部類實例保持對其父類實例的引用。所以內部類的線程比你想象的要多得多。在非常相關的情況下,如果您使用的是序列化,並且已經序列化了臨時對象,則需要通過調用ObjectOutputStream.reset()來清除序列化緩存,否則您的進程將泄漏內存並最終死亡。 不利的一面是非瞬態對象將被重新序列化。 序列化臨時結果對象可能比您想象的要混亂得多!

考慮使用軟引用。如果你不知道什麼是軟引用,請閱讀javadoc中的java.lang.ref.SoftReference

避開幻影引用和弱引用,除非你確實感到興奮。

最後,如果你真的不能容忍GC使用Realtime Java。

不,我不是在開玩笑。

參考實現是免費下載和彼得Dibbles從SUN書是非常好的閱讀。

+0

我會考慮'WeakReference`的用例比`SoftReference`更強。如果Foo爲Foo的好處需要引用「Bar」,它應該使用強引用。如果`Foo`需要對Bar的好處引用`Bar`,它應該使用弱引用。例如,'Foo'可能會在每次'Foo'做某事時收到通知,但'Foo'如果不需要通知任何人,就會同樣高興。如果只有對「Bar」的引用由不關心它是否存在的事物持有,則它不應該存在。 – supercat 2013-05-06 17:05:22

0

避免終結器。不能保證他們會及時被召喚。在內存管理系統(即垃圾收集器)決定用終結器收集對象之前可能需要相當長的時間。

很多人使用終結器來做一些事情,比如關閉套接字連接或刪除臨時文件。通過這樣做,您可以使應用程序的行爲不可預知,並與JVM何時將GC對象關聯。這可能導致「內存不足」情況,這不是由於Java堆耗盡,而是由於系統用盡特定資源的句柄。

要記住的另一件事是,將調用引入System.gc()或此類錘子可能會在您的環境中顯示良好的結果,但它們不一定會轉換爲其他系統。不是每個人都運行相同的JVM,有很多,SUN,IBM J9,BEA JRockit,Harmony,OpenJDK等等......這個JVM全都符合JCK(那些已經過官方測試的),但有很多自由當談到快速製造事物時。 GC是每個人都大量投資的領域之一。使用錘子往往會摧毀這種努力。

3

至於終結走:

  1. 他們幾乎是無用的。他們不能保證以及時的方式被調用,甚至根本不會(如果GC從不運行,也不會有任何終結器)。這意味着你通常不應該依賴他們。
  2. 終結者不保證是冪等的。垃圾收集器非常注意確保它永遠不會在同一個對象上多次調用finalize()。對於寫得很好的對象,這並不重要,但是如果對象寫得不好,多次調用finalize會導致問題(例如,原生資源的雙重釋放......崩潰)。
  3. 每個具有finalize()方法的對象還應該提供一個close()(或類似的)方法。這是你應該調用的功能。例如FileInputStream.close()。當你有一個更合適的方法打算由你打電話時,沒有理由打電話給finalize()
0

你根本不會調用finalize方法。

1

完成操作時關閉操作系統句柄的真正問題是最終操作沒有保證順序執行。但是,如果你有處理阻塞的東西(比如套接字),你的代碼可能會陷入死鎖狀態(根本不是微不足道的)。

所以我是以可預測的有序方式顯式關閉句柄。對於資源的處理應遵循的模式基本上代碼:

SomeStream s = null; 
... 
try{ 
    s = openStream(); 
    .... 
    s.io(); 
    ... 
} finally { 
    if (s != null) { 
     s.close(); 
     s = null; 
    } 
} 

,如果你寫你自己的類,通過JNI和打開的句柄的工作變得更加複雜。你需要確保把手關閉(釋放),並且它只會發生一次。桌面J2SE中經常被忽視的操作系統句柄是Graphics[2D]。即使BufferedImage.getGrpahics()也可能會返回指向視頻驅動程序的句柄(實際上將資源保存在GPU上)。如果你不會自己釋放它,並讓垃圾收集器來完成這項工作 - 當你用完視頻卡映射的位圖但仍有足夠的內存時,你可能會發現奇怪的OutOfMemory和類似的情況。根據我的經驗,它在與圖形對象(提取縮略圖,縮放,銳化命名)的緊密循環中經常發生。

基本上GC不負責程序員對正確資源管理的責任。它只關心記憶,沒有別的。 Stream.finalize調用close()恕我直言將更好地實施拋出異常新的RuntimeError(「垃圾收集仍然打開的流」)。在馬虎愛好者離開終點後,它將節省數小時和數天的調試和清潔代碼。

快樂編碼。

和平。