Effective Java 2.0_中英文對照_Item 7

文章作者:Tyan
博客:noahsnail.com | CSDN | 簡書

Item 7: Avoid finalizers

Finalizers are unpredictable, often dangerous, and generally unnecessary.Their use can cause erratic behavior, poor performance, and portability problems. Finalizers have a few valid uses, which we’ll cover later in this item, but as a rule of thumb, you should avoid finalizers.

終結方法通常是不可預測的,經常是危險的,一般來說是沒必要的。使用它們會引起不穩定的行為,性能變低,可移植性問題等。終結方法有一些有效的使用,這個在本條目的后面會講到,但根據經驗,你應該避免使用終結方法。

C++ programmers are cautioned not to think of finalizers as Java’s analog of C++ destructors. In C++, destructors are the normal way to reclaim the resources associated with an object, a necessary counterpart to constructors. In Java, the garbage collector reclaims the storage associated with an object when it becomes unreachable, requiring no special effort on the part of the programmer. C++ destructors are also used to reclaim other nonmemory resources. In Java, the try-finally block is generally used for this purpose.

C++程序員被警告說不要去想像Java中模擬C++析構函數那樣的終結方法。在C++中,析構函數是一種正?;厥諏ο筚Y源的方式,是構造函數的必要對應。在Java中,當對象不可訪問時,垃圾回收器會回收對象的相關資源,不需要程序員進行專門的工作。C++析構函數也用來回收其它的非內存資源。在Java中,try-finally塊用來完成這樣的功能。

One shortcoming of finalizers is that there is no guarantee they’ll be executed promptly [JLS, 12.6]. It can take arbitrarily long between the time that an object becomes unreachable and the time that its finalizer is executed. This means that you should never do anything time-critical in a finalizer. For example, it is a grave error to depend on a finalizer to close files, because open file descriptors are a limited resource. If many files are left open because the JVM is tardy in executing finalizers, a program may fail because it can no longer open files.

終結方法的一個缺點是不能保證它們及時的執行[JLS,12.6]。從對象變得不可訪問開始到它的終結方法被執行結束,這中間的時間可以任意長。這意味著你不應該在終結方法中做任何時間為關鍵的事情。例如,依賴終結方法來關閉文件是一個嚴重的錯誤,因為開放的文件描述符是一種有限的資源。如果許多文件都是打開狀態,由于JVM執行終結方法時是遲緩的,因此程序可能失敗,因為它不能再打開文件。

The promptness with which finalizers are executed is primarily a function of the garbage collection algorithm, which varies widely from JVM implementation to JVM implementation. The behavior of a program that depends on the promptness of finalizer execution may likewise vary. It is entirely possible that such a program will run perfectly on the JVM on which you test it and then fail miserably on the JVM favored by your most important customer.

盡快執行終結方法是垃圾回收算法的主要功能,在不同的JVM實現中變化很大。依賴終結方法執行及時性的程序同樣變化很大。一個程序在測試它的JVM上運行非常完美,但在你最重要客戶支持的JVM上它卻糟糕地運行失敗了,這是完全有可能的。

Tardy finalization is not just a theoretical problem. Providing a finalizer for a class can, under rare conditions, arbitrarily delay reclamation of its instances. A colleague debugged a long-running GUI application that was mysteriously dying with an OutOfMemoryError. Analysis revealed that at the time of its death, the application had thousands of graphics objects on its finalizer queue just waiting to be finalized and reclaimed. Unfortunately, the finalizer thread was running at a lower priority than another application thread, so objects weren’t getting finalized at the rate they became eligible for finalization. The language specification makes no guarantees as to which thread will execute finalizers, so there is no portable way to prevent this sort of problem other than to refrain from using finalizers.

遲緩終結不僅僅是一個理論問題。在很少的情況下,為一個類提供終結方法可能會隨意地延遲它實例的回收。有個同事調試一個長期運行的GUI應用,程序莫名其妙的死掉了,拋出了OutOfMemoryError錯誤。分析表明在程序死亡時,應用中的終結方法隊列中有成千上萬的圖形對象在等待被終結并回收。遺憾的是,終結方法線程的運行優先級要低于另一個應用線程,因此在另一個應用線程中的對象變得可以被終結時,它們不能被終結。語言規范不能保證哪一個線程來執行終結方法,因此沒有輕便的方式來阻止這種問題的發生,除非避免使用終結方法。

Not only does the language specification provide no guarantee that finalizers will get executed promptly; it provides no guarantee that they’ll get executed at all. It is entirely possible, even likely, that a program terminates without executing finalizers on some objects that are no longer reachable. As a consequence, you should never depend on a finalizer to update critical persistent state. For example, depending on a finalizer to release a persistent lock on a shared resource such as a database is a good way to bring your entire distributed system to a grinding halt.

不僅語言規范不能保證終結方法及時的執行;而且也不能保證終結方法得到執行。這完全有可能,甚至有可能一個程序終止時,一些不能訪問的對象的終結方法都沒有執行。結論就是:你從不該依賴終結方法來更新重要的持續狀態。例如,依賴一個終結方法來釋放一個共享資源,例如數據庫,的持續鎖,很容易引起整個分布式系統突然當掉。

Don’t be seduced by the methods System.gc and System.runFinalization. They may increase the odds of finalizers getting executed, but they don’t guarantee it. The only methods that claim to guarantee finalization are System.runFinalizersOnExit and its evil twin, Runtime.runFinalizersOnExit. These methods are fatally flawed and have been deprecated [ThreadStop].

不要被System.gcSystem.runFinalization方法誘惑。它們可能會增加終結方法得到執行的幾率,但它們不能保證它。能保證終結方法執行的唯一方法是System.runFinalizersOnExit以及它臭名昭著的孿生兄弟Runtime.runFinalizersOnExit。這些方法都有致命的缺陷并且已經被廢棄了[ThreadStop]。

In case you are not yet convinced that finalizers should be avoided, here’s another tidbit worth considering: if an uncaught exception is thrown during finalization, the exception is ignored, and finalization of that object terminates [JLS, 12.6]. Uncaught exceptions can leave objects in a corrupt state. If another thread attempts to use such a corrupted object, arbitrary nondeterministic behavior may result. Normally, an uncaught exception will terminate the thread and print a stack trace, but not if it occurs in a finalizer—it won’t even print a warning.

以防你還不相信終結方法應該被避免,這兒有另一個情況值得思考:如果在終結方法執行期間拋出了一個無法捕獲的異常,這個異常被忽略了,對象的終結方法終止了[JLS,12.6]。不能捕獲的異??赡軙箤ο筇幱诒罎顟B。如果另一個線程試圖使用這樣一個崩潰的對象,任何不確定性的行為都有可能發送。通常,一個未被捕獲的異常會終止線程并打印棧軌跡,但如果它發生在一個終結方法中,將不會打印出警告。

Oh, and one more thing: there is a severe performance penalty for using finalizers. On my machine, the time to create and destroy a simple object is about 5.6 ns. Adding a finalizer increases the time to 2,400 ns. In other words, it is about 430 times slower to create and destroy objects with finalizers.

哦,還有一件事:使用終結方法會有嚴重的性能問題。在我的機器上,創建并銷毀一個簡單對象大約是5.6納秒。添加一個終結方法會將這個時間增加到2400納秒。換句話說,創建一個對象并用終結方法銷毀對象比正常情況下大約慢了430倍。

So what should you do instead of writing a finalizer for a class whose objects encapsulate resources that require termination, such as files or threads? Just provide an explicit termination method, and require clients of the class to invoke this method on each instance when it is no longer needed. One detail worth mentioning is that the instance must keep track of whether it has been terminated: the explicit termination method must record in a private field that the object is no longer valid, and other methods must check this field and throw an IllegalStateException if they are called after the object has been terminated.

因此當一個類的對象封裝的資源需要結束時,你應該用什么來代替一個類的終結方法?例如文件或線程?提供一個顯式的結束方法,當類的實例不再需要時,要求類的客戶端在每個實例上都調用這個方法。一個值得提及的細節是,實例必須跟蹤它是否已經被終結:顯式的結束方法必須記錄在一個私有字段中,這個字段表明對象不再有效,如果其它方法再對象終結后調用對象,其它方法必須檢查這個字段并拋出IllegalStateException

Typical examples of explicit termination methods are the close methods on InputStream, OutputStream, and java.sql.Connection. Another example is the cancel method on java.util.Timer, which performs the necessary state change to cause the thread associated with a Timer instance to terminate itself gently. Examples from java.awt include Graphics.dispose and Window.dispose. These methods are often overlooked, with predictably dire performance consequences. A related method is Image.flush, which deallocates all the resources associated with an Image instance but leaves it in a state where it can still be used, reallocating the resources if necessary.

顯式結束方法的典型例子是InputStream,OutputStreamjava.sql.Connection的關閉方法。另一個例子是java.util.Timercancel方法,它會進行必要的狀態檢查并一起線程相關的Timer實例平穩的結束它自己。java.awt的例子包括Graphics.disposeWindow.dispose。這些方法經常被忽視,可以預料會引起可怕的性能后果。一個相關的方法是Image.flush,它會釋放所有Image實例相關的資源,但會將實例保持在一個可用的狀態,如果必要的時候重新分配資源。

Explicit termination methods are typically used in combination with the try-finally construct to ensure termination. Invoking the explicit termination method inside the finally clause ensures that it will get executed even if an exception is thrown while the object is being used:

顯式結束方法通過與try-finally結構結合來確保終結。在finally語句塊的內部調用顯式的結束方法來確保它得到執行,即使對象使用時拋出了一個異常:

// try-finally block guarantees execution of termination method
   Foo foo = new Foo(...);
   try {
       // Do what must be done with foo
       ...
   } finally {
       foo.terminate();  // Explicit termination method
   }

So what, if anything, are finalizers good for? There are perhaps two legitimate uses. One is to act as a “safety net” in case the owner of an object forgets to call its explicit termination method. While there’s no guarantee that the finalizer will be invoked promptly, it may be better to free the resource late than never, in those (hopefully rare) cases when the client fails to call the explicit termination method. But the finalizer should log a warning if it finds that the resource has not been terminated, as this indicates a bug in the client code, which should be fixed. If you are considering writing such a safety-net finalizer, think long and hard about whether the extra protection is worth the extra cost.

那終結方法有什么好處呢?有兩種可能的合法應用。一個是作為『安全網』,以防對象擁有者忘記調用它的顯式結束方法。但這不能保證終結方法得到及時的調用,當客戶端調用顯式結束方法失敗時,在那種情況下(希望很少),后面釋放資源總比不釋放資源要好。但終結方法如果發現資源仍沒有被釋放,它應該輸出一個警告,因為這意味著客戶端代碼存在一個BUG,它應該被修正。如果你正在考慮寫這樣一個安全網終結方法,要仔細思考這種額外的保護是否值得額外的代價。

The four classes cited as examples of the explicit termination method pattern (FileInputStream, FileOutputStream, Timer, and Connection) have finalizers that serve as safety nets in case their termination methods aren’t called. Unfortunately these finalizers do not log warnings. Such warnings generally can’t be added after an API is published, as it would appear to break existing clients.

作為顯式結束方法模式引用的四個例子(FileInputStream,FileOutputStream,TimerConnection)都有終結方法作為安全網以防它們的結束方法沒有被調用。遺憾的是這些終結方法不輸出警告。這種警告通常在API發布后不能進行添加,因為它會損壞現有的客戶端。

A second legitimate use of finalizers concerns objects with native peers. A native peer is a native object to which a normal object delegates via native methods. Because a native peer is not a normal object, the garbage collector doesn’t know about it and can’t reclaim it when its Java peer is reclaimed. A finalizer is an appropriate vehicle for performing this task, assuming the native peer holds no critical resources. If the native peer holds resources that must be terminated promptly, the class should have an explicit termination method, as described above. The termination method should do whatever is required to free the critical resource. The termination method can be a native method, or it can invoke one.

終結方法的第二個合法使用是關于對象的本地對等體。本地對等體是一個本地對象,普通對象通過本地方法委托給本地對象。由于本地對等體不是一個正常的對象,當它的Java對等體回收時,垃圾回收器不知道并且不能回收它。假設本地對等體不擁有重要的資源,終結方法是執行這個任務的合適工具。如果本地對等體擁有必須及時終止的資源,這個類應該有一個顯式的結束方法,如上所述。結束方法應該用來釋放重要資源。結束方法可以是一個本地方法或它可以調用一個本地方法。

It is important to note that “finalizer chaining” is not performed automatically. If a class (other than Object) has a finalizer and a subclass overrides it, the subclass finalizer must invoke the superclass finalizer manually. You should finalize the subclass in a try block and invoke the superclass finalizer in the corresponding finally block. This ensures that the superclass finalizer gets executed even if the subclass finalization throws an exception and vice versa. Here’s how it looks. Note that this example uses the Override annotation (@Override), which was added to the platform in release 1.5. You can ignore Override annotations for now, or see Item 36 to find out what they mean:

很重要的一點就是要注意『終結方法鏈』是不能自動執行的。如果一個類(不是Object)有一個終結方法,一個子類覆寫了它,子類終結方法必須手動調用父類終結方法。你應該try塊內終止這個子類并在對應的finally塊調用父類終結方法。這保證了父類終結方法得到了執行,即使子類終結方法拋出異常,反之亦然。下面是它的一個例子、注意這個例子使用了Override注解(@Override),在release 1.5版本中添加?,F在你可以忽略Override注解,或看Item 36弄明白它是什么意思:


// Manual finalizer chaining
@Override 
protected void finalize() throws Throwable {
    try {
        ... // Finalize subclass state
    } finally {
        super.finalize();
    } 
}

If a subclass implementor overrides a superclass finalizer but forgets to invoke it, the superclass finalizer will never be invoked. It is possible to defend against such a careless or malicious subclass at the cost of creating an additional object for every object to be finalized. Instead of putting the finalizer on the class requiring finalization, put the finalizer on an anonymous class (Item 22) whose sole purpose is to finalize its enclosing instance. A single instance of the anonymous class, called a finalizer guardian, is created for each instance of the enclosing class. The enclosing instance stores the sole reference to its finalizer guardian in a private instance field so the finalizer guardian becomes eligible for finalization at the same time as the enclosing instance. When the guardian is finalized, it performs the finalization activity desired for the enclosing instance, just as if its finalizer were a method on the enclosing class:

如果一個子類實現者覆寫了父類的終結方法但忘了調用它,父類終結方法將會從未調用。要注意防范這種粗心的或邪惡的子類是有可能的,代價就是為每個要被終結的對象創建一個額外的對象。代替將終結方法放在需要終結的類中,將終結方法放在一個匿名類中(Item 22),它的唯一目的就是終結它外圍實例。匿名類的單個實例,被稱為終結方法守護者,為外圍類的每個實例創建一個匿名類實例。外圍實例在一個私有字段存儲了它的終結方法守護者的唯一引用,因此終結方法守護者與外圍實例可以同時進行終結。當守護者被終結時,它會執行外圍實例要求的終結活動,就像它的終結方法是外圍實例的一個方法一樣:

// Finalizer Guardian idiom
public class Foo {
    // Sole purpose of this object is to finalize outer Foo object
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
            ... // Finalize outer Foo object
        }
    };
    ...  // Remainder omitted
}

Note that the public class, Foo, has no finalizer (other than the trivial one it inherits from Object), so it doesn’t matter whether a subclass finalizer calls super.finalize or not. This technique should be considered for every nonfinal public class that has a finalizer.

注意公有類Foo沒有終結方法(除非它從Object繼承一個無關緊要的),因此子類的終結方法是否調用super.finalize是不重要的。每一個含有終結方法的非終結公有類都應該考慮這個技術。

In summary, don’t use finalizers except as a safety net or to terminate noncritical native resources. In those rare instances where you do use a finalizer, remember to invoke super.finalize. If you use a finalizer as a safety net, remember to log the invalid usage from the finalizer. Lastly, if you need to associate a finalizer with a public, nonfinal class, consider using a finalizer guardian, so finalization can take place even if a subclass finalizer fails to invoke super.finalize.

總結:不要使用終結方法,除非是用作安全網或用來終止一個非重要的本地資源。在那些你使用終結方法的稀少實例中,記住調用super.finalize。如果你使用終結方法作為安全網,記住在終結方法中輸出非法用法。最后,如果你需要將終結方法關聯到一個公有的,非終結類,考慮使用終結方法守護者,即使子類終結方法調用super.finalize失敗,也會進行終結。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 看到這個畫面讓我想到,曾經的我們也做過這樣的事情:班里大掃除,擦玻璃,擦板凳,掃地,擦黑板?,F在想來是那么的云...
    五月天和芒果先生閱讀 267評論 0 1
  • 有一位朋友遇到了這樣的問題:她大學畢業沒有選擇就業,而是與談戀愛四年的男友,也就是現在的先生結婚了,當了全職太太,...
    舒小曼閱讀 666評論 8 15
  • 第十一節復焙(1) 茶葉烘焙,熱度愈高時,水蒸氣中飛散之物質,喪失量亦愈大。故茶之烘焙,須將熱度減至有效的最低限度...
  • 黑暗在淤集 漫無邊際 火車發出遲緩的嘆息 太陽裹緊了七彩的大衣 繁星對著明月喃喃自語 風拂過死寂的沙 顯露出被時光...
    夏目家的夏木閱讀 246評論 0 3