前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >是时候忘掉finalize方法了

是时候忘掉finalize方法了

作者头像
ImportSource
发布2018-04-03 11:52:30
1.7K0
发布2018-04-03 11:52:30
举报
文章被收录于专栏:ImportSourceImportSource

故事要从jdk11 early access版说起。

近日,发现jdk11发布了一个早鸟版。心想,jdk10刚刚发布没多久(JDK10要来了:下一代 Java 有哪些新特性?),jdk那帮人就开始搞jdk11了。

发现没?“ThreadPoolExecutor不应该再有finalize的依赖了”。也就是ThreadPoolExecutor中的下面的方法要被去掉了:

代码语言:javascript
复制
/**
 * 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方法返回后,对象不会被轻易的收集。相反,垃圾回收器必须重新运行才能确定对象是否真正无法访问,从而决定是否收集。

代码语言:javascript
复制
/**
 * 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方法,用来清除包含的引用。

代码语言:javascript
复制
/**
 * 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很有可能就会被去掉了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ImportSource 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档