文章作者: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.
終結(jié)方法通常是不可預(yù)測的,經(jīng)常是危險的,一般來說是沒必要的。使用它們會引起不穩(wěn)定的行為,性能變低,可移植性問題等。終結(jié)方法有一些有效的使用,這個在本條目的后面會講到,但根據(jù)經(jīng)驗,你應(yīng)該避免使用終結(jié)方法。
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++析構(gòu)函數(shù)那樣的終結(jié)方法。在C++中,析構(gòu)函數(shù)是一種正?;厥諏ο筚Y源的方式,是構(gòu)造函數(shù)的必要對應(yīng)。在Java中,當(dāng)對象不可訪問時,垃圾回收器會回收對象的相關(guān)資源,不需要程序員進(jìn)行專門的工作。C++析構(gòu)函數(shù)也用來回收其它的非內(nèi)存資源。在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.
終結(jié)方法的一個缺點是不能保證它們及時的執(zhí)行[JLS,12.6]。從對象變得不可訪問開始到它的終結(jié)方法被執(zhí)行結(jié)束,這中間的時間可以任意長。這意味著你不應(yīng)該在終結(jié)方法中做任何時間為關(guān)鍵的事情。例如,依賴終結(jié)方法來關(guān)閉文件是一個嚴(yán)重的錯誤,因為開放的文件描述符是一種有限的資源。如果許多文件都是打開狀態(tài),由于JVM執(zhí)行終結(jié)方法時是遲緩的,因此程序可能失敗,因為它不能再打開文件。
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.
盡快執(zhí)行終結(jié)方法是垃圾回收算法的主要功能,在不同的JVM實現(xiàn)中變化很大。依賴終結(jié)方法執(zhí)行及時性的程序同樣變化很大。一個程序在測試它的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.
遲緩終結(jié)不僅僅是一個理論問題。在很少的情況下,為一個類提供終結(jié)方法可能會隨意地延遲它實例的回收。有個同事調(diào)試一個長期運行的GUI應(yīng)用,程序莫名其妙的死掉了,拋出了OutOfMemoryError錯誤。分析表明在程序死亡時,應(yīng)用中的終結(jié)方法隊列中有成千上萬的圖形對象在等待被終結(jié)并回收。遺憾的是,終結(jié)方法線程的運行優(yōu)先級要低于另一個應(yīng)用線程,因此在另一個應(yīng)用線程中的對象變得可以被終結(jié)時,它們不能被終結(jié)。語言規(guī)范不能保證哪一個線程來執(zhí)行終結(jié)方法,因此沒有輕便的方式來阻止這種問題的發(fā)生,除非避免使用終結(jié)方法。
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.
不僅語言規(guī)范不能保證終結(jié)方法及時的執(zhí)行;而且也不能保證終結(jié)方法得到執(zhí)行。這完全有可能,甚至有可能一個程序終止時,一些不能訪問的對象的終結(jié)方法都沒有執(zhí)行。結(jié)論就是:你從不該依賴終結(jié)方法來更新重要的持續(xù)狀態(tài)。例如,依賴一個終結(jié)方法來釋放一個共享資源,例如數(shù)據(jù)庫,的持續(xù)鎖,很容易引起整個分布式系統(tǒng)突然當(dāng)?shù)簟?/p>
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.gc和System.runFinalization方法誘惑。它們可能會增加終結(jié)方法得到執(zhí)行的幾率,但它們不能保證它。能保證終結(jié)方法執(zhí)行的唯一方法是System.runFinalizersOnExit以及它臭名昭著的孿生兄弟Runtime.runFinalizersOnExit。這些方法都有致命的缺陷并且已經(jīng)被廢棄了[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.
以防你還不相信終結(jié)方法應(yīng)該被避免,這兒有另一個情況值得思考:如果在終結(jié)方法執(zhí)行期間拋出了一個無法捕獲的異常,這個異常被忽略了,對象的終結(jié)方法終止了[JLS,12.6]。不能捕獲的異??赡軙箤ο筇幱诒罎顟B(tài)。如果另一個線程試圖使用這樣一個崩潰的對象,任何不確定性的行為都有可能發(fā)送。通常,一個未被捕獲的異常會終止線程并打印棧軌跡,但如果它發(fā)生在一個終結(jié)方法中,將不會打印出警告。
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.
哦,還有一件事:使用終結(jié)方法會有嚴(yán)重的性能問題。在我的機器上,創(chuàng)建并銷毀一個簡單對象大約是5.6納秒。添加一個終結(jié)方法會將這個時間增加到2400納秒。換句話說,創(chuàng)建一個對象并用終結(jié)方法銷毀對象比正常情況下大約慢了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.
因此當(dāng)一個類的對象封裝的資源需要結(jié)束時,你應(yīng)該用什么來代替一個類的終結(jié)方法?例如文件或線程?提供一個顯式的結(jié)束方法,當(dāng)類的實例不再需要時,要求類的客戶端在每個實例上都調(diào)用這個方法。一個值得提及的細(xì)節(jié)是,實例必須跟蹤它是否已經(jīng)被終結(jié):顯式的結(jié)束方法必須記錄在一個私有字段中,這個字段表明對象不再有效,如果其它方法再對象終結(jié)后調(diào)用對象,其它方法必須檢查這個字段并拋出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.
顯式結(jié)束方法的典型例子是InputStream,OutputStream和java.sql.Connection的關(guān)閉方法。另一個例子是java.util.Timer的cancel方法,它會進(jìn)行必要的狀態(tài)檢查并一起線程相關(guān)的Timer實例平穩(wěn)的結(jié)束它自己。java.awt的例子包括Graphics.dispose和Window.dispose。這些方法經(jīng)常被忽視,可以預(yù)料會引起可怕的性能后果。一個相關(guān)的方法是Image.flush,它會釋放所有Image實例相關(guān)的資源,但會將實例保持在一個可用的狀態(tài),如果必要的時候重新分配資源。
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:
顯式結(jié)束方法通過與try-finally結(jié)構(gòu)結(jié)合來確保終結(jié)。在finally語句塊的內(nèi)部調(diào)用顯式的結(jié)束方法來確保它得到執(zhí)行,即使對象使用時拋出了一個異常:
// 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.
那終結(jié)方法有什么好處呢?有兩種可能的合法應(yīng)用。一個是作為『安全網(wǎng)』,以防對象擁有者忘記調(diào)用它的顯式結(jié)束方法。但這不能保證終結(jié)方法得到及時的調(diào)用,當(dāng)客戶端調(diào)用顯式結(jié)束方法失敗時,在那種情況下(希望很少),后面釋放資源總比不釋放資源要好。但終結(jié)方法如果發(fā)現(xiàn)資源仍沒有被釋放,它應(yīng)該輸出一個警告,因為這意味著客戶端代碼存在一個BUG,它應(yīng)該被修正。如果你正在考慮寫這樣一個安全網(wǎng)終結(jié)方法,要仔細(xì)思考這種額外的保護是否值得額外的代價。
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.
作為顯式結(jié)束方法模式引用的四個例子(FileInputStream,FileOutputStream,Timer和Connection)都有終結(jié)方法作為安全網(wǎng)以防它們的結(jié)束方法沒有被調(diào)用。遺憾的是這些終結(jié)方法不輸出警告。這種警告通常在API發(fā)布后不能進(jìn)行添加,因為它會損壞現(xiàn)有的客戶端。
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.
終結(jié)方法的第二個合法使用是關(guān)于對象的本地對等體。本地對等體是一個本地對象,普通對象通過本地方法委托給本地對象。由于本地對等體不是一個正常的對象,當(dāng)它的Java對等體回收時,垃圾回收器不知道并且不能回收它。假設(shè)本地對等體不擁有重要的資源,終結(jié)方法是執(zhí)行這個任務(wù)的合適工具。如果本地對等體擁有必須及時終止的資源,這個類應(yīng)該有一個顯式的結(jié)束方法,如上所述。結(jié)束方法應(yīng)該用來釋放重要資源。結(jié)束方法可以是一個本地方法或它可以調(diào)用一個本地方法。
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:
很重要的一點就是要注意『終結(jié)方法鏈』是不能自動執(zhí)行的。如果一個類(不是Object)有一個終結(jié)方法,一個子類覆寫了它,子類終結(jié)方法必須手動調(diào)用父類終結(jié)方法。你應(yīng)該try塊內(nèi)終止這個子類并在對應(yīng)的finally塊調(diào)用父類終結(jié)方法。這保證了父類終結(jié)方法得到了執(zhí)行,即使子類終結(jié)方法拋出異常,反之亦然。下面是它的一個例子、注意這個例子使用了Override注解(@Override),在release 1.5版本中添加?,F(xiàn)在你可以忽略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:
如果一個子類實現(xiàn)者覆寫了父類的終結(jié)方法但忘了調(diào)用它,父類終結(jié)方法將會從未調(diào)用。要注意防范這種粗心的或邪惡的子類是有可能的,代價就是為每個要被終結(jié)的對象創(chuàng)建一個額外的對象。代替將終結(jié)方法放在需要終結(jié)的類中,將終結(jié)方法放在一個匿名類中(Item 22),它的唯一目的就是終結(jié)它外圍實例。匿名類的單個實例,被稱為終結(jié)方法守護者,為外圍類的每個實例創(chuàng)建一個匿名類實例。外圍實例在一個私有字段存儲了它的終結(jié)方法守護者的唯一引用,因此終結(jié)方法守護者與外圍實例可以同時進(jìn)行終結(jié)。當(dāng)守護者被終結(jié)時,它會執(zhí)行外圍實例要求的終結(jié)活動,就像它的終結(jié)方法是外圍實例的一個方法一樣:
// 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沒有終結(jié)方法(除非它從Object繼承一個無關(guān)緊要的),因此子類的終結(jié)方法是否調(diào)用super.finalize是不重要的。每一個含有終結(jié)方法的非終結(jié)公有類都應(yīng)該考慮這個技術(shù)。
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.
總結(jié):不要使用終結(jié)方法,除非是用作安全網(wǎng)或用來終止一個非重要的本地資源。在那些你使用終結(jié)方法的稀少實例中,記住調(diào)用super.finalize。如果你使用終結(jié)方法作為安全網(wǎng),記住在終結(jié)方法中輸出非法用法。最后,如果你需要將終結(jié)方法關(guān)聯(lián)到一個公有的,非終結(jié)類,考慮使用終結(jié)方法守護者,即使子類終結(jié)方法調(diào)用super.finalize失敗,也會進(jìn)行終結(jié)。