Java中的finalize()方法是如何工作的?

5

我最近发现了Java中的finalize方法(不确定为什么之前错过了,但是它就在那里)。这似乎可以解决我正在处理的许多问题,但我想先获取更多信息。

在线上,我找到了这张图,说明垃圾收集和finalize的过程:

This describes the order of operations involving finalize and the JGC:

有几个问题:

  1. 这是在一个单独的线程中进行的,对吧?
  2. 如果我在finalize期间实例化一个新对象,那么会发生什么?这样做是否允许?
  3. 如果我从finalize中调用静态方法会发生什么?
  4. 如果我在finalize内部建立一个新的对该对象的引用会发生什么?

我想我应该解释一下为什么我感兴趣。我经常使用LWJGL,并且似乎如果我可以使用finalize来自动清理OpenGL资源的Java对象,那么我可以在API方面做一些非常好的事情。


2
这并不是你问题的直接答案,但要知道finalize不能保证被调用,这也是为什么它没有被广泛使用的众多原因之一。(编辑:即使它被调用了,也是由于Java管理内存的内存压力而被调用。这意味着,如果你试图使用它来释放其他资源,那么你可能会耗尽它们并且程序崩溃,即使清理可以回收所有资源,只是因为Java不知道运行GC将回收(例如)文件句柄。) - jacobm
3个回答

5

当Java垃圾回收器检测到没有对特定对象的引用时,将调用finalize()。通过Object类,所有Java对象都继承了finalize()。

据我所知,您可以在finalize()方法中进行静态方法调用,并且可以从finalize()建立一个新引用;但是我认为这是一种糟糕的编程实践。

不应依赖于finalize()来清理资源,最好在使用过后就清理资源。我更喜欢使用try、catch、finally来清理资源,而不是使用finalize()。特别地,如果使用finalize(),则意味着JVM必须保留你的finalizable对象引用的所有其他对象,以防它们进行调用。这意味着你要占用可能不需要使用的内存。更重要的是,这也意味着你可能会导致JVM永远无法处理对象,因为它们必须保留它们以防另一个对象的finalize()方法需要使用它们(例如,竞争条件)。

此外,请考虑GC不被调用的情况。因此,您不能确保finalize()将被调用。

我的建议是,在完成使用资源后立即清理,不要依赖finalize()来清理资源。


3
我认为没有任何保证哪个线程会被使用。新对象可能会被实例化,静态方法可能会被调用。建立一个对你的对象的新引用将防止它被垃圾回收,但是finalize方法将不会再次被调用--你不想这样做。
清理资源正是finalize方法的作用,所以你应该没问题。不过有几个警告:
该方法不能保证被调用。如果你占用了资源,当程序停止时这些资源将不会自动释放,请不要依赖于finalize
该方法被调用的时间不能保证。内存紧张时,这将更早发生。有大量可用内存时,如果发生的话,它将更晚甚至根本不会发生。这可能适合你:如果有大量内存,你可能并不太关心释放资源。(虽然挂起它们可能会干扰同时运行的其他软件,在这种情况下,你会关心它。)
我的通常解决方案是拥有某种dispose方法来进行清理。如果可以的话,我会在某个时候明确地调用它,并尽快调用它。然后我添加一个仅调用dispose方法的finalize方法。(请注意,dispose方法在多次调用时必须表现良好!事实上,在这种编程方式中,我可能会在finalize之外多次调用dispose,不确定以前的调用是否成功,并且希望它尽快有效地被调用。) 现在,理想情况下,我的资源将在我不再需要它们时立即释放。然而,如果我失去了拥有资源的对象,当内存不足并且我需要帮助时,finalize方法将帮助我脱离困境。

1
首先,要记住并没有保证所有对象都会运行最终器。您可以使用它来释放与对象关联的本地代码分配的内存,但对于纯Java代码,大多数用例仅用于执行清理资源的“备份”机制。这意味着在大多数情况下,您应该手动释放资源,并且最终器只能作为一种帮助程序来清理资源,以防您忘记按照标准方式进行清理。但是,您不能将它们用作清理的唯一或主要机制。更一般地说,您不应编写任何依赖于运行最终器的代码的正确性。
广告1. 据我所知,没有保证哪个线程调用finalize(),尽管实际上这可能是GC线程之一。

广告2. 允许实例化新对象。但是,在处理终结器中的对象引用时存在一些陷阱。特别是,如果您在某个活动对象中存储对正在完成的对象的硬引用,则可能会防止清理您即将进行垃圾回收的对象。这种对象复活可能会导致资源耗尽,如果失控的话。此外,请注意finalize()中的异常-它们可能会停止终结器,但程序无法自动了解它们。您需要将代码包装在try-catch块中并自行传播信息。此外,终结器的长时间执行可能会导致对象队列积累并消耗大量内存。一些其他值得注意的问题和限制在this JavaWorld article中描述。

广告3. 在终结器中调用静态方法不应该有任何问题。

广告4. 如第2点所述,通过在终结期间将对其的引用放置在另一个活动对象中,可以防止对象被垃圾回收(使其复活)。然而,这是棘手的行为,可能不是好的做法。

总之,您不能依赖终结器来清理资源。您需要手动处理,并且在您的情况下,终结器最多只能用作备份机制,以某种程度上弥补粗糙编码的后果。这意味着,不幸的是,您使用终结器来清理OpenGL资源以使API更好的想法可能行不通。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接