★finalize 函數的調用機制程序員
俺常常囉嗦:「瞭解本質機制的重要性」。因此今天也得先談談 finalize 函數的調用機制。在聊以前,先聲明一下:Java虛擬機規範(見「這裏」),並無硬性規定垃圾回收該不應搞,以及該如何搞。因此俺這裏提到的 finalize 函數的調用機制,或許適用於大多數 JVM,但【不保證】適用於全部的 JVM。數據庫
◇什麼時候被調用?安全
finalize 啥時候纔會被調用捏?通常來講,要等到JVM開始進行垃圾回收的時候,它才【有可能】被調用。而 JVM 進行垃圾回收的時間點是【很是】不肯定的,依賴於各類運行時的環境因素。具體細節能夠參見「本系列前一帖」。正是因爲 finalize 函數調用時間點的不肯定,致使了後面提到的某些缺點。性能優化
◇誰來調用?ide
說完什麼時候調用,咱接着來聊一下被誰調用?
常見的 JVM 會經過 GC 的垃圾回收線程來進行 finalize 函數的調用。因爲垃圾回收線程比較重要(人家好歹也是 JVM 的一個組成部分嘛),爲了防止 finalize 函數拋出的異常影響到垃圾回收線程的運做,垃圾回收線程會在調用每個 finalize 函數時進行 try/catch,若是捕獲到異常,就直接丟棄,而後接着處理下一個失效對象的 finalize 函數。函數
★對 finalize 函數的誤解和誤用性能
◇把 finalize 理解爲「析構函數」優化
學過 C++ 的同窗應該都知道「析構函數」(不懂 C++ 的同窗直接跳過此小節)。C++ 析構函數是在對象離開做用域的當口,【當即】被調用的。
不少從 C++ 轉 Java 的同窗會想固然地把 Java 的 finalize 函數牽強附會成 C++ 的析構函數(二者確實有某些類似之處)。然而,現實每每不是這麼美好滴。因爲 Java 的 finalize 函數和 C++ 的析構函數之間有許多很是【關鍵性】的差別,那些把 finalize 拿來當析構函數用的同窗,是註定要碰壁滴(具體請看本文後面「finalize 函數的缺點」)。this
◇依靠 finalize 來釋放資源線程
不少同窗寄但願於經過 finalize() 來完成類對象中某些資源的釋放(好比關閉數據庫鏈接之類)。
有這種企圖的同窗,請注意看本文後面的「finalize 函數的缺點」!
★使用 finalize 函數的注意事項
下面介紹的注意事項,有些可能和性能優化關係不大,俺也一併列出來。
◇調用時間不肯定——有資源浪費的風險
前面已經介紹了調用機制。同窗們應該認清【finalize 的調用時機是很不肯定的】這樣一個事實。因此,假如你把某些稀缺資源放到 finalize() 中釋放,可能會致使該稀缺資源等上好久好久好久之後才被釋放。這但是資源的浪費啊!
另外,某些類對象所攜帶的資源(好比某些 JDBC 的類)可能自己就很耗費內存,這些資源的延遲釋放會形成很大的性能問題。
◇可能不被調用——有資源泄漏的風險
不少同窗誤覺得 finalize() 老是會被調用,【其實否則】。在某些狀況下,finalize() 壓根兒不被調用。好比在 JVM 退出的當口,內存中那些對象的 finalize 函數可能就不會被調用了。
俺估摸着:還有同窗在打 「runFinalizersOnExit」 的主意,來確保全部的 finalize 在 JVM 退出前被調用。可是,很惋惜也很遺憾,該方法從 JDK 1.2 開始,就已經被廢棄了。即便該方法不被廢棄,也是有很大的線程安全隱患滴!企圖打這個主意的同窗,趁早死了這條心吧!
從上述能夠看出,一旦你依賴 finalize() 來幫你釋放資源,那但是很不妙啊(【有資源泄漏的危險】)!關於資源泄漏的嚴重性,俺在「這裏」曾經提到過。不少時候,資源泄露致使的性能問題更加嚴重,萬萬不可小看。
◇對象可能在 finalize 函數調用時復活——有詐屍的風險
詐屍的狀況比較少見,不過俺仍是稍微提一下。
原本,只有當某個對象已經失效(沒有引用),垃圾回收器纔會調用該對象的 finalize 函數。可是,萬一碰上某個變態的程序員,在 finalize() 函數內部再把對象自身的引用(也就是 this)從新保存在某處,也就至關於把本身復活了(由於這個對象從新有了引用,再也不處於失效狀態)。這種作法是否是夠變態啊 :-)
爲了防止發生這種詭異的事情,垃圾回收器只能在每次調用完 finalize() 以後再次去檢查該對象是否還處於失效狀態。這無形中又增長了 JVM 的開銷。
隨便提一下。因爲 JDK 的文檔中規定了(具體參見「這裏」),JVM 對於每個類對象實例最多隻會調用一次 finalize()。因此,對於那些詐屍的實例,當它們真正死亡時,finalize() 反而不會被調用了。這看起來是否是很奇怪?
◇要記得本身作異常捕獲
剛纔在介紹 finalize() 調用機制時提到,一旦有異常拋出到 finalize 函數外面,會被垃圾回收線程捕獲並丟棄。也就是說,異常被忽略掉了(異常被忽略的危害,「這裏」有提到)。爲了防止這種事兒,凡是 finalize() 中有可能拋出異常的代碼,你都得寫上 try catch 語句,本身進行捕獲。
◇要當心線程安全
因爲調用 finalize() 的是垃圾回收線程,和你本身代碼的線程不是同一個線程;甚至不一樣對象的 finalize() 可能會被不一樣的垃圾回收線程調用(好比使用「並行收集器」的時候)。因此,當你在 finalize() 裏面訪問某些數據的時候,還得時刻留心線程安全的問題。
★結論
前面廢了這麼多話,最後稍微總結一下。俺竊覺得:finalize 實在是 Java 的雞肋。或許它對於【極少數】程序員有用,但對於大多數人(包括俺自個兒),這玩意兒壓根兒沒啥好處。大夥兒仍是儘可能不用爲妙。