歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Effective Java - 避免使用finalizer

Effective Java - 避免使用finalizer

日期:2017/3/1 9:34:38   编辑:Linux編程

Finalizers are unpredictable ,often dangerous ,and generally unnecessary.

在Java中,GC會自動回收不可達對象相關的空間,而不需要程序員做相關的工作。對於非內存資源,我們通常使用try-finally語句塊進行釋放。

finalizer不保證立即執行。
從一個對象編程不可達狀態到調用finalizer,這段時間是任意的。
即,對時間敏感的操作不能在finalizer中進行。

never do anything time-critical in finalizer.

比如對一些資源對象進行close,file descriptor是有限資源,不及時關閉的後果很嚴重。
(PS:話說AutoCloseable的author也是Josh Bloch。)

哪些finalizer會及時得到執行,這個問題主要依賴於GC算法。
但GC算法在不同的JVM中的表現或多或少會不一樣(我做過的項目都是用Hotspot...)。
一個程序可能在開發時運行得不錯,而換了個環境可能會無法正常運行。
(比如finalizer的優先級太低,一直沒得到回收,回收速度跟不上創建速度。)

不僅不保證finalizer執行的及時性,而且也不保證finalizer執行的可能性。
finalizer有可能根本得不到執行。
比如,很多不可達對象的finalizer尚未得到執行,此時程序被終止。

Never depend on a finalizer to update critical persistent state.

也不要期待以下方法為finalizer的執行提供保障:

System.gc();

System.runFinalization();

Runtime.runFinalizersOnExit(true);

System.runFinalizersOnExit(true);


值得一提的是,如果未捕獲的異常在finalizer中拋出,這個異常會被無視掉,而且連stack trace都不會打印出來。
另外,finalizer有非常嚴重的性能損耗,這種東西最好盡量避免。

如果某個對象封裝的資源(比如文件或者線程)需要終止,此時也不要指望finalizer。
而是提供一個顯示的close方法(explicit termination method),並且加入一個記錄資源可用狀態的私有域。

那finalizer到底有什麼意義?

  • 當忘記調用close方法時,finalizer可以當作最後的防線(原話是safety net,即安全網)。
    比如FileInputStream的close方法和finalizer,當私有的FileDescriptor不為空並且也不屬於java.lang.System#in時調用顯示的close方法:

    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
    
            /*
             * Finalizer should not release the FileDescriptor if another
             * stream is still using it. If the user directly invokes
             * close() then the FileDescriptor is also released.
             */
            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }
  • 處理本地對等體(native peer)。
    之前沒有太關注"本地對等體"這個詞匯。
    本地對等體是一個native對象,普通對象通過本地方法委托給一個本地對象。
    GC無法回收本地對等體,當本地對等體的java對等體被回收的時候,本地對等體不會被處理。
    如果本地對等體不用有關鍵資源(critical resource),finalizer可以勝任這項工作。

PS:對於本地對等體的解釋,可以參考下面一段話:

一個AWT組件通常是一個包含了對等體接口類型引用的組件類。這個引用指向本地對等體實現。
java.awt.Label為例,它的對等體接口是LabelPeer。LabelPeer是平台無關的。
在不同平台上,AWT提供不同的對等體類來實現LabelPeer。在Windows上,對等體類是WlabelPeer,它調用JNI來實現label的功能。
這些JNI方法用C或C++編寫。它們關聯一個本地的label,真正的行為都在這裡發生。
作為整體,AWT組件由AWT組件類和AWT對等體提供了一個全局公用的API給應用程序使用。
一個組件類和它的對等體接口是平台無關的。底層的對等體類和JNI代碼是平台相關的。

(An AWT component is usually a component class which holds a reference with a peer interface type. This reference points to a native peer implementation.
Take java.awt.Label for example, its peer interface is LabelPeer.
LabelPeer is platform independent. On every platform, AWT provides different peer class which implements LabelPeer. On Windows, the peer class is WlabelPeer, which implement label functionalities by JNI calls.
These JNI methods are coded in C or C++. They do the actual work, interacting with a native label.
Let's look at the figure.
You can see that AWT components provide a universal public API to the application by AWT component class and AWT peers. A component class and its peer interface are identical across platform. Those underlying peer classes and JNI codes are different. )


另外,提到了finalizer chaining。 子類構造器會自動調用父類構造器,於是可能會想象子類finalizer自動調用父類finalizer。 如果子類復寫了父類的finalizer,子類必須手動調用父類的finalier。

Copyright © Linux教程網 All Rights Reserved