是时候忘掉finalize方法了

故事要从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(importsource)

原文发表时间:2018-03-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木可大大

编写优雅代码的最佳实践

Robert Martin曾说过"在代码阅读中说脏话的频率是衡量代码质量额唯一标准"。同时,代码的写法应当使别人理解它所需的时间最小化,也就是说我们写的代码是给...

10320
来自专栏极客猴

Python 中连接字符串效率最高的方式是哪种呢?

在编码过程中,我们经常需要对字符串进行连接处理操作。如果我们能使用优雅的方式来处理字符串连接,那么程序内存开销会小很多。

11120
来自专栏生信宝典

Python学习 - 可视化变量赋值、循环、程序运行过程

Python Tutor (http://www.pythontutor.com/)是`Philip Guo`开发的,通过把计算机运行程序代码的过程可视化的展示...

26580
来自专栏个人随笔

Java工具类 通过ResultSet对象返回对应的实体List集合

44150
来自专栏walterlv - 吕毅的博客

.NET Core/Framework 创建委托以大幅度提高反射调用的性能

发布于 2018-02-07 09:45 更新于 2018-02...

10010
来自专栏企鹅号快讯

30分钟学会用Python编写简单程序

参与文末每日话题讨论,赠送异步新书 异步图书君 学习目标 知道有序的软件开发过程的步骤。 了解遵循输入、处理、输出(IPO)模式的程序,并能够以简单的方式修改它...

729100
来自专栏老九学堂

1分钟彻底理解C语言指针的概念

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用4个字节,char 占用1个字节。为了正确地访问这些数据,必须为每个字节...

53880
来自专栏LeoXu的博客

Flex笔记_格式化数据 原

注意:上述代码没有输出结果是因为Flex内部会把XML转换成一组高级对象,既不是Date也不是String,而format函数只接受这两种对象作为参数,因此代码...

8920
来自专栏技术小黑屋

MissingFormatArgumentException: Format Specifier 'S'

贴出一个简单的异常,分析一下原因,以及推荐一个相对好一些的替代方法。 如下,如果我们进行字符串格式化提供的值的数量少于字符串格式符(%s)的数量,就会抛出Mis...

29420
来自专栏北京马哥教育

Python 的正则表达式彩蛋

虽然我觉得在 Python 的标准库里的确有不少很恶心的库,但是 re 库肯定不属于这种。尽管它真的有年头没有更新了,但是在我看来,仍不失为动态语言中最好的库...

30270

扫码关注云+社区

领取腾讯云代金券