终结器对JVM的性能影响

根据这篇文章 ,在.Net中,

终结者实际上甚至比这更糟糕。 除了它们运行较晚(这对于许多种资源确实是一个严重的问题),它们也不那么强大,因为它们只能执行析构函数中允许的操作的子集(例如,终结器不能可靠地使用其他对象,而析构函数可以),即使在写入子集时,终结器也很难正确编写。 并且收集可终结的对象是昂贵的:每个可终结的对象,以及可从其获得的对象的潜在巨大图形,被提升到下一代GC生成,这使得通过一些大的倍数收集它更加昂贵。

这是否也适用于一般的JVM,特别是HotSpot?

以下是Effective Java 2nd Edition的一些选择引用:第7项:避免终结器

终结器是不可预测的,通常是危险的,并且通常是不必要的 。 它们的使用会导致不稳定的行为,性能不佳和可移植性问题。 终结器几乎没有有效用途,[…]作为经验法则,你应该避免使用终结器。

你应该确保事实上,你需要终结器; 大多数时候你不这样做。

提醒C ++程序员不要将终结器视为Java的C ++析构函数的类比。 在C ++中,析构函数是回收与对象关联的资源的常用方法,对象是构造函数的必要对应物。 在Java中,垃圾收集器在它变得无法访问时回收与对象相关联的存储,而不需要程序员的特殊努力。 C ++析构函数也用于回收其他非内存资源。 在Java中, try-finally块通常用于此目的。

调用终结器的语义也很重要:

JLS 12.6类实例的最终确定

Java编程语言没有指定调用finalizer时间[…也不会]哪个线程将为任何给定对象调用终结器。 […]如果在完成期间抛出未捕获的exception,则忽略该exception并终止该对象的终止。 (JLS 12.6.2)终结器调用未订购

而且,按需运行终结器的唯一机制就被打破了。 以下引用来自Effective Java 2nd Edition:

[…]声称保证最终确定的唯一方法是System.runFinalizersOnExit及其邪恶的双胞胎Runtime.runFinalizersOnExit 。 这些方法存在致命缺陷,已被弃用。

布洛赫进一步评论了性能惩罚(强调他):

哦,还有一件事: 使用终结器会有严重的性能损失 。 在我的机器上,创建和销毁一个简单对象的时间约为5.6ns。 添加终结器可将时间增加到2,400ns。 换句话说,使用终结器创建和销毁对象的速度要慢430倍。

由于基准测试方法的细节很少,我认为具体数字并不重要,但它确实证实了广泛记录的内容:终结器非常昂贵。

这本书确实解释了使用终结器有效的罕见场景。 从这个答案中省略这些引用是故意的。

以下是2004年的明确声明:

具有终结器的对象(具有非平凡的finalize()方法的对象)与没有终结器的对象相比具有显着的开销,并且应该谨慎使用。 可完成对象的分配速度较慢,收集速度较慢。 在分配时,JVM必须使用垃圾收集器注册任何可终结对象,并且(至少在HotSpot JVM实现中)可完成对象必须遵循比大多数其他对象更慢的分配路径。 同样,可完成对象的收集速度也较慢。 在可回收可完成对象之前,它至少需要两次垃圾收集周期(在最好的情况下),并且垃圾收集器必须做额外的工作来调用终结器。 结果是花费更多时间分配和收集对象以及对垃圾收集器施加更多压力,因为无法访问的可终结对象使用的内存保留的时间更长。 将其与终结器无法保证在任何可预测的时间范围内运行甚至根本不运行这一事实相结合,您可以看到相对较少的情况,最终确定是正确的工具。

Java有一个非常类似的终结器机制。 这是一篇关于终结者的好文章: http : //www.javaworld.com/javaworld/jw-06-1998/jw-06-techniques.html

Java中的一般经验法则是不要使用它们。

finalize()当垃圾收集确定没有对该对象的更多引用时,由对象上的垃圾收集器调用。 子类重写finalize方法以处置系统资源或执行其他清理。

不要使用终结器,主要是因为不可预测,我们不知道什么 时候会被执行,“不要试图比JVM更聪明”