我一直在阅读很多关于finalize()的Java新手问题,并发现没有人真正清楚地表明finalize()是一种不可靠的清理资源的方法,这有点令人困惑。我看到有人评论说他们用它来清理连接,这真的很可怕,因为唯一接近于保证连接关闭的方法是最后实现try (catch)。
我没有学过CS,但我已经用Java专业编程近十年了,我从来没有见过有人在生产系统中实现finalize()。这并不意味着它没有用处,或者和我一起工作的人一直在做正确的事情。
所以我的问题是,实现finalize()有哪些用例不能通过语言中的另一个进程或语法更可靠地处理?
请提供具体的场景或您的经验,简单地重复Java教科书,或最终确定的预期用途是不够的,因为这不是这个问题的意图。
您可以将它用作持有外部资源(套接字、文件等)的对象的支持。实现close()方法和需要调用它的文档。
如果检测到close()没有执行,则实现finalize()来执行close()处理。可能会将一些东西转储到stderr,以指出您正在清理有错误的调用者。
它在异常/有bug的情况下提供了额外的安全性。并不是每个调用者每次都会执行正确的try {} finally{}。不幸的是,但在大多数环境中都是如此。
我同意很少有人需要它。正如评论者指出的那样,它带来了GC开销。只有当你在一个长期运行的应用程序中需要“皮带和背带裤”的安全性时才使用。
我看到从Java 9开始,Object.finalize()已弃用!它们将我们指向java.lang.ref.Cleaner和java.lang.ref.PhantomReference作为替代。
finalize()是对JVM的一个提示,建议在未指定的时间执行代码。当您希望代码莫名其妙地无法运行时,这是很好的方法。
在终结器中做任何重要的事情(基本上除了日志)在三种情况下也很好:
您希望打赌其他已完成的对象仍然处于程序其余部分认为有效的状态。
您希望向所有具有终结器的类的所有方法添加大量检查代码,以确保它们在终结后行为正确。
您希望意外地复活已完成的对象,并花费大量时间试图弄清楚为什么它们不起作用,和/或为什么它们在最终释放时没有最终完成。
If you think you need finalize(), sometimes what you really want is a phantom reference (which in the example given could hold a hard reference to a connection used by its referand, and close it after the phantom reference has been queued). This also has the property that it may mysteriously never run, but at least it can't call methods on or resurrect finalized objects. So it's just right for situations where you don't absolutely need to close that connection cleanly, but you'd quite like to, and the clients of your class can't or won't call close themselves (which is actually fair enough - what's the point of having a garbage collector at all if you design interfaces that require a specific action be taken prior to collection? That just puts us back in the days of malloc/free.)
Other times you need the resource you think you're managing to be more robust. For example, why do you need to close that connection? It must ultimately be based on some kind of I/O provided by the system (socket, file, whatever), so why can't you rely on the system to close it for you when the lowest level of resource is gced? If the server at the other end absolutely requires you to close the connection cleanly rather than just dropping the socket, then what's going to happen when someone trips over the power cable of the machine your code is running on, or the intervening network goes out?
免责声明:我以前在JVM实现上工作过。我讨厌终结者。
公认的答案是好的,我只是想补充的是,现在有一种方法来实现finalize的功能,而不实际使用它。
看看“Reference”类。弱引用,幻影引用和软引用。
您可以使用它们来保存对所有对象的引用,但是这个引用ALONE不会停止GC。这样做的好处是,当它被删除时,你可以让它调用一个方法,并且这个方法可以保证被调用。
至于finalize:
我使用finalize一次来了解哪些对象正在被释放。你可以用静态、引用计数等来玩一些巧妙的游戏——但这只是为了分析,但要注意这样的代码(不只是在finalize中,但这是你最有可能看到它的地方):
public void finalize() {
ref1 = null;
ref2 = null;
othercrap = null;
}
这表明某人不知道自己在做什么。像这样的“清理”实际上是不需要的。当类被GC化时,这将自动完成。
如果你在finalize中发现这样的代码,那肯定是编写它的人搞糊涂了。
如果它在其他地方,可能是代码是一个坏模型的有效补丁(一个类存在很长一段时间,由于某种原因,它引用的东西必须在对象GC之前手动释放)。一般来说,这是因为有人忘记删除监听器或其他东西,不知道为什么他们的对象没有被GC,所以他们只是删除它引用的东西,耸耸肩然后离开。
它永远不应该被用来“更快”地清理东西。
一个简单的规则:永远不要使用终结器。
对象具有终结器(不管它执行什么代码)这一事实本身就足以导致相当大的垃圾收集开销。
摘自Brian Goetz的一篇文章:
Objects with finalizers (those that
have a non-trivial finalize() method)
have significant overhead compared to
objects without finalizers, and should
be used sparingly. Finalizeable
objects are both slower to allocate
and slower to collect. At allocation
time, the JVM must register any
finalizeable objects with the garbage
collector, and (at least in the
HotSpot JVM implementation)
finalizeable objects must follow a
slower allocation path than most other
objects. Similarly, finalizeable
objects are slower to collect, too. It
takes at least two garbage collection
cycles (in the best case) before a
finalizeable object can be reclaimed,
and the garbage collector has to do
extra work to invoke the finalizer.
The result is more time spent
allocating and collecting objects and
more pressure on the garbage
collector, because the memory used by
unreachable finalizeable objects is
retained longer. Combine that with the
fact that finalizers are not
guaranteed to run in any predictable
timeframe, or even at all, and you can
see that there are relatively few
situations for which finalization is
the right tool to use.
class MyObject {
Test main;
public MyObject(Test t) {
main = t;
}
protected void finalize() {
main.ref = this; // let instance become reachable again
System.out.println("This is finalize"); //test finalize run only once
}
}
class Test {
MyObject ref;
public static void main(String[] args) {
Test test = new Test();
test.ref = new MyObject(test);
test.ref = null; //MyObject become unreachable,finalize will be invoked
System.gc();
if (test.ref != null) System.out.println("MyObject still alive!");
}
}
====================================
结果:
This is finalize
MyObject still alive!
=====================================
所以你可以在finalize方法中使一个不可达的实例可达。
编辑:好吧,这真的不管用。我实现了它,并认为如果它有时失败了,这对我来说是可以的,但它甚至没有调用finalize方法一次。
我不是一个专业的程序员,但在我的程序中,我有一个案例,我认为是一个使用finalize()的好例子,那是一个缓存,在它被销毁之前将其内容写入磁盘。因为它没有必要在每次销毁时都执行,它只会加速我的程序,我希望我没有做错。
@Override
public void finalize()
{
try {saveCache();} catch (Exception e) {e.printStackTrace();}
}
public void saveCache() throws FileNotFoundException, IOException
{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
out.writeObject(cache);
}
就我个人而言,除了在一种罕见的情况下,我几乎从不使用finalize():我创建了一个自定义泛型类型集合,并编写了一个自定义finalize()方法,它执行以下操作:
public void finalize() throws Throwable {
super.finalize();
if (destructiveFinalize) {
T item;
for (int i = 0, l = length(); i < l; i++) {
item = get(i);
if (item == null) {
continue;
}
if (item instanceof Window) {
((Window) get(i)).dispose();
}
if (item instanceof CompleteObject) {
((CompleteObject) get(i)).finalize();
}
set(i, null);
}
}
}
(CompleteObject是我做的一个接口,让你指定你已经实现了很少实现的对象方法,如#finalize(), #hashCode()和#clone()))
因此,使用姐妹方法# setdestructivelyfinalizer(布尔值),使用我的集合的程序可以(帮助)确保销毁对该集合的引用也会销毁对其内容的引用,并处理可能在无意中保持JVM活动的任何窗口。我也考虑过停止任何线程,但这打开了一个全新的蠕虫罐头。
资源(文件,套接字,流等)需要关闭一旦我们完成他们。它们通常有close()方法,我们通常在try-catch语句的finally部分调用该方法。有时finalize()也可以被少数开发人员使用,但在我看来,这不是一种合适的方式,因为不能保证finalize总是被调用。
在Java 7中,我们有try-with-resources语句,可以这样使用:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
// Processing and other logic here.
} catch (Exception e) {
// log exception
} finally {
// Just in case we need to do some stuff here.
}
在上面的例子中,try-with-resource将通过调用close()方法自动关闭资源BufferedReader。如果我们愿意,我们也可以在自己的类中实现Closeable,并以类似的方式使用它。在我看来,这似乎更整洁,更容易理解。