6.4 终结机制
有一种古老的资源管理技术叫终结(finalization),开发者应该知道有这么一种技术。然而,这种技术几乎完全废弃了,任何情况下,大多数 Java 开发者都不应该直接使用。
只有少数应用场景适合使用终结,而且只有少数 Java 开发者会遇到这种场景。如果有任何疑问,就不要使用终结,处理资源的
try语句往往是正确的替代品。
终结机制的作用是自动释放不再使用的资源。垃圾回收自动释放的是对象使用的内存资源,不过对象可能会保存其他类型的资源,例如打开的文件和网络连接。垃圾回收程序不会为你释放这些额外的资源,因此,终结机制的作用是让开发者执行清理任务,例如关闭文件、中断网络连接、删除临时文件,等等。
终结机制的工作方式是这样的:如果对象有 finalize() 方法(一般叫作终结方法),那么不再使用这个对象(或对象不可达)后的某个时间会调用这个方法,但要在垃圾回收程序回收分配给这个对象的空间之前调用。终结方法用于清理对象使用的资源。
在 Oracle/OpenJDK 中,按照下述方式使用这种技术。
(1) 如果可终结的对象不可达了,会在内部终结队列中放一个引用,指向这个对象;而且,为了回收垃圾,这个对象会被标记为“存活”。
(2) 对象一个接着一个从终结队列中移除,然后调用各自的 finalize() 方法。
(3) 调用终结方法后,不会立即释放对象,因为终结方法可能会把 this 引用存储在某个地方(例如在某个类的公开静态字段中),让对象再次拥有引用,复活对象。
(4) 因此,调用 finalize() 方法后,垃圾回收子系统在回收对象之前,必须重新判断对象是否可达。
(5) 不过,就算对象复活了,也不会再次调用终结方法。
(6) 综上所述,定义了 finalize() 方法的对象一般(至少)会多存活一个 GC 循环(如果是生命期长的对象,会再多存活一个完整的 GC 循环)。
终结机制的主要问题是,Java 不确定什么时候回收垃圾,或者以什么顺序回收对象。因此,Java 平台无法确认什么时候(甚至是否)调用终结方法,或者以什么顺序调用终结方法。
因此,作为一种防止资源(例如文件句柄)稀少的自动清理机制,其设计是有缺陷的,因为不能保证终结机制运行得足够快,避免耗尽资源。
终结方法唯一真正有用的场景是,在一个类中使用本地方法,打开某个非 Java 资源。就算遇到这种情况,也更适合使用处理资源的块状 try 语句,但也可以声明一个 public native finalize() 方法(close() 方法会调用这个方法)——这个方法可以释放本地资源,包括不受 Java 垃圾回收程序控制的堆外内存。
终结机制的细节
为了少数适合使用终结机制的场景,下面列出一些额外细节,以及使用过程中的注意事项。
在没有回收全部重要的对象之前,JVM 可能就会退出,所以根本不会调用某些终结方法。遇到这种情况,操作系统会关闭网络连接等资源,并将其回收。然而,要注意,如果要删除文件的终结方法没有运行,操作系统不会删除那个文件。
为了确保在虚拟机退出前执行某些操作,Java 提供了
Runtime::addShutdownHook钩子,在 JVM 退出前安全执行任意代码。finalize()方法是实例方法,作用在实例上。没有等效的机制用来终结类。终结方法是实例方法,没有参数,也不返回值。每个类只能有一个终结方法,而且必须命名为
finalize()。终结方法可以抛出任何类型的异常或错误,但垃圾回收子系统自动调用终结方法时,终结方法抛出的任何异常或错误都会被忽略,这些异常或错误只会导致终结方法返回。
只有少数应用场景适合使用终结,而且只有少数 Java 开发者会遇到这种场景。如果有任何疑问,就不要使用终结,处理资源的 