故事要从jdk11 early access版说起。
近日,发现jdk11发布了一个早鸟版。心想,jdk10刚刚发布没多久(JDK10要来了:下一代 Java 有哪些新特性?),jdk那帮人就开始搞jdk11了。
发现没?“ThreadPoolExecutor不应该再有finalize的依赖了”。也就是ThreadPoolExecutor中的下面的方法要被去掉了:
/**
* Invokes {@code shutdown} when this executor is no longer
* referenced and it has no threads.
*/
protected void finalize() {
shutdown();
}
仔细进去看看了bug提交记录:
有个叫Roger Riggs的兄弟提了这个bug。他说Object的finalize方法都已经变得“deprecation”了。希望ThreadPoolExecutor也同步把这个finalize方法给去掉或者替换成其他解决方案吧。
追根溯源,那么Object的finalize方法为什么要逐渐退出历史的舞台呢?
首先得从c++说起。
Java的Finalizer与C++的析构函数
Java的finalizer的作用与C++析构函数不同。在C++中(在引入类似shared_ptr之类的机制之前),只要你在构造函数中使用new创建了某些内容,就需要在析构函数中调用delete。于是人们就错误地将这种想法带到了Java中,他们认为有必要编写finalize方法来清除对其他对象的引用(其实根本没有这个必要,其实我们很少在使用这个方法了)。 在Java中,垃圾收集器会清理堆中的任何东西,所以根本没必要使用finalize方法来做这件事情。
如果对象创建不由垃圾收集器管理的资源,则finalize非常有用。例如,文件描述符或本地分配内存,也就是堆外内存。垃圾收集器不会清理这些东西,所以这时候就只能用finalize来搞定。在Java早期,finalize是清理非堆资源的唯一机制。
虚引用(Phantom References)
1、finalize的问题
finalize方法的有个问题就是,它允许对象的“复活”。当一个对象的finalize方法被调用时,它可以引用this(即将被收集的对象)。这个时候就可以将this这个引用又挂载回对象图(object graph)中,从而逃过了对象收集。因此,在finalize方法返回后,对象不会被轻易的收集。相反,垃圾回收器必须重新运行才能确定对象是否真正无法访问,从而决定是否收集。
/**
* finalize的时候逃过gc
*/
public class Resurrection
{
public static Resurrection obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Resurrection finalize called !!!");
obj = this;//在finalize方法中复活对象
}
@Override
public String toString() {
return "I am Resurrection";
}
public static void main(String[] args) throws InterruptedException {
obj = new Resurrection();
obj = null; //将obj设为null
System.gc();//垃圾回收
Thread.sleep(1000);//
if(obj == null) {
System.out.println("obj is null");
} else {
System.out.println("obj is alive");
}
System.out.println("第2次调用gc后");
obj = null;//由于obj被复活,此处再次将obj设为null
System.gc();//再次gc
Thread.sleep(1000);
if(obj == null) {
//对象的finalize方法仅仅会被调用一次,所以可以预见再次设置obj为null后,obj会被垃圾回收,该语句会被调用
System.out.println("obj is null");
} else {
System.out.println("obj is alive");
}
}
}
2、使用虚引用的好处
在JDK 1.2中引入了引用包java.lang.ref。该软件包包含了几种不同的引用类型,其中之一就是虚引用(PhantomReference)。 PhantomReference的显著特点就是它不允许对象“复活”。它通过使得包含的引用不可访问来实现这一点。虚引用的持有者会得到通知,引用对象已变得无法到达(严格来说,叫虚幻可达),但无法将引用对象取出并将其挂回到对象图(object graph)中。这使得垃圾收集器的工作变得更轻松。
PhantomReference的另一个优点是,像其他引用类型一样,它可以被显式清除。假设现在有一个对象hold一些外部资源,比如文件描述符(file descriptor)。通常的话,这些对象有一个close方法用来释放资源。在引入引用类型之前,这些对象还需要一个finalize方法来清理应用程序未能调用close的情况。问题是,即使应用程序调用了close方法,收集器也需要执行最终化处理,然后再次运行。
PhantomReference和其他引用类型一样,都有个clear方法,用来清除包含的引用。
/**
* Clears this reference object. Invoking this method will not cause this
* object to be enqueued.
*
* <p> This method is invoked only by Java code; when the garbage collector
* clears references it does so directly, without invoking this method.
*/
public void clear() {
this.referent = null;
}
clear方法在Reference<T>抽象类中,具体的引用类型都继承自此类
通过显式调用close方法释放其原生资源的对象将调用PhantomReference.clear。这避免了一系列的引用处理步骤,让对象在不可达的时候立即被收集。
Java 9引入的java.lang.ref.Cleaner
Java 9引入了java.lang.ref.Cleaner这个类。此类号称是对finalize方法的替代。
引用处理通常是比较敏感的,而且需要做很多事情,你要创建一个引用queue,而且还需要一个线程去处理queue里的那些引用。Cleaner就是一个ReferenceQueue和PhantomReference的封装,使得处理引用变得更加简单了。
最后
其实一直以来人们对于finalize这个方法都保持谨慎态度,现在官方正式的放弃了它,在下面的这本书中也曾经有对finalize这个方法的类似观点。
现在是时候和finalize方法说拜拜了,如果你的项目中还留存部分finalize方法,那么建议你早点做出调整。在jdk11的时候,ThreadPoolExecutor里的finalize很有可能就会被去掉了。
本文分享自 ImportSource 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!