JEP 421: Java将要终结finalize()了!


finalize()在未来的版本中,默认情况下将被禁用,在以后的版本中它将被删除。依赖于最终确定的库和应用程序的维护者应该考虑迁移到其他资源管理技术,例如try-with-resources 语句cleaners

Java 程序能自动内存管理,其中 JVM 的垃圾收集器 (GC) 在不再需要对象时回收该对象使用的内存。但是,某些对象表示操作系统提供的资源,例如打开的文件描述符或本机内存块。对于这样的对象,仅仅回收对象的内存是不够的;程序还必须将底层资源释放回操作系统,通常是通过调用对象的close方法。如果程序在 GC 回收对象之前未能执行此操作,则释放资源所需的信息将丢失。操作系统仍认为正在使用的资源已泄漏。
ava 1.0 中引入的finalize()旨在帮助避免资源泄漏:一个个类可以声明一个终结方法:protected void finalize();
该finalize方法可以执行诸如调用对象的close方法之类的操作。乍一看,这似乎是防止资源泄漏的有效安全网,实际上,finalize()利用垃圾收集的力量来管理非内存资源(Barry Hayes,收集器接口中的终结,内存管理国际研讨会,1992)。
不幸的是,最终确定有几个关键的基本缺陷:

  • 不可预测的延迟——在对象变得不可访问的那一刻和它的finalize被调用的那一刻之间可能会经过任意长的时间。事实上,GC 不保证任何finalize都会被调用。
  • 不受约束的行为——finalize代码可以采取任何行动。特别是,它可以保存对正在终结的对象的引用,从而复活该对象并使其再次可达。
  • 始终启用— finalize没有明确的注册机制。带有finalize的类可以对类的每个实例进行终结,无论是否需要。不能取消对象的终结,即使该对象不再需要它。
  • 未指定的线程——finalize以任意顺序在未指定的线程上运行。线程和排序都无法控制。

这些缺陷在二十多年前就被广泛认可。它在 Java 平台中的存在给整个生态系统带来了负担,因为它将所有库和应用程序代码暴露在安全性、可靠性和性能风险中。它还对 JDK,特别是 GC 实现施加了持续的维护和开发成本。为了推动 Java 平台向前发展,我们将弃用finalize以进行移除。
鉴于与最终确定相关的问题,开发人员应该使用替代技术来避免资源泄漏,即try-with-resources 和清洁器。
  • Try-with-resources — Java 7 引入了try-with-resources 语句,作为对上述try-finally结构的改进。
    try-with-resources 正确处理所有异常情况,避免了对finalize安全网的需要。在单个词法范围内打开和关闭的任何资源都应转换为与try-with-resources 一起使用。如果带有finalize的类的实例只能在try-with-resources 语句中使用,则finalize可能是不必要的,可以删除。
  • Cleaner ——有些资源的生命try周期太长,无法与-with-resources很好地配合使用,因此 Java 9 引入了CleanerAPI 来帮助释放它们。Cleaner API 允许程序为对象注册清理操作,该操作在对象变得不可访问后的一段时间运行。清理操作避免了finalize器的许多缺点:

但是,与终结器一样,清理操作是由 GC 安排的,因此它们可能会受到无限延迟的影响。因此,在需要及时释放资源的情况下,不应使用更​​清洁的 API。
 
将最终弃用java.base和java.desktop模块中的这些方法
java.lang.Object.finalize()
java.lang.Enum.finalize()
java.awt.Graphics.finalize()
java.awt.PrintJob.finalize()
java.util.concurrent.ThreadPoolExecutor.finalize()
javax.imageio.spi.ServiceRegistry.finalize()
javax.imageio.stream.FileCacheImageInputStream.finalize()
javax.imageio.stream.FileImageInputStream.finalize()
javax.imageio.stream.FileImageOutputStream.finalize()
javax.imageio.stream.ImageInputStreamImpl.finalize()
javax.imageio.stream.MemoryCacheImageInputStream.finalize()

此外,我们将:
  • 最终弃用java.lang.Runtime.runFinalization()和java.lang.System.runFinalization(). 这些方法没有最终确定就没有任何意义。
  • 弃用模块接口中的getObjectPendingFinalizationCount()方法