finalize方法

本文是Reference相关内容的最后一篇了。在这一篇,我们介绍一下 FinalReference 和 finalize方法。

finalize方法定义在Object中:

protected void finalize() throws Throwable { }

在对象中我们可以重定义这个方法。在这个方法中可以释放各种资源。关于这一点,相信大多数人都比较熟悉了。但是关于 finalize 到底什么时候被调用,好像没有人能说清楚。我们来看一下 hotspot 到底是怎么来实现的。

首先,hotspot 里会把类的构造方法的最后一条指令,也就是Java虚拟机指令的return,重写为一个特殊的指令:

void Rewriter::rewrite_Object_init(methodHandle method, TRAPS) {
  RawBytecodeStream bcs(method);
  while (!bcs.is_last_bytecode()) {
    Bytecodes::Code opcode = bcs.raw_next();
    switch (opcode) {
      case Bytecodes::_return: *bcs.bcp() = Bytecodes::_return_register_finalizer; break;
// 以下代码略

这个指令,在解释器里是这样被执行的:

if (_desc->bytecode() == Bytecodes::_return_register_finalizer) {
    assert(state == vtos, "only valid state");
    __ movptr(c_rarg1, aaddress(0));
    __ load_klass(rdi, c_rarg1);
    __ movl(rdi, Address(rdi, Klass::access_flags_offset()));
    __ testl(rdi, JVM_ACC_HAS_FINALIZER);
    Label skip_register_finalizer;
    __ jcc(Assembler::zero, skip_register_finalizer);
        
    __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::register_finalizer), c_rarg1);
        
    __ bind(skip_register_finalizer);
  }   

这其实是一段手写的汇编,大概是这样的,Klass结构的指针先加载到rdi寄存器,然后,去取得它的acess_flag,去检查这个Klass是否定义了finalize方法。如果一个类里定义了finalize,那么在加载的时候,就会使它的access_flag中的JVM_ACC_HAS_FINALIZER置位。

也就是说,如果这个Klass中定义了finalize方法,就会调用 register_finalizer 这个方法。这个方法我们就不去看了,有兴趣的同学自己去查。

反正最终呢,会调用到Finalizer.java中定义的这个类中:

  /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

就是说,如果一个定义了finalize方法的类在初始化的时候,就会调用一下这个static方法,生成一个Finalizer对象,而我们自己的对象就是这个方法中所使用的finalizee。

也就是说,我们新建一个带 finalize 方法的对象,就会伴生一个 Finalizer 对象。我们看一下,这个类的定义:

final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
                                                          same package as the Reference
                                                          class */

    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private static Finalizer unfinalized = null;
    private static final Object lock = new Object();

    private Finalizer
        next = null,
        prev = null;

    private boolean hasBeenFinalized() {
        return (next == this);
    }

    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    private void remove() {
        synchronized (lock) {
            if (unfinalized == this) {
                if (this.next != null) {
                    unfinalized = this.next;
                } else {
                    unfinalized = this.prev;
                }
            }
            if (this.next != null) {
                this.next.prev = this.prev;
            }
            if (this.prev != null) {
                this.prev.next = this.next;
            }
            this.next = this;   /* Indicates that this has been finalized */
            this.prev = this;
        }
    }

    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }

Hoho,绕了半天,这和上节课所讲的Cleaner何其相似,都是一个双向队列,在构造的时候就把新建的Finalizer对象加入到一个双向链表中。

注意上面这段代码里的queue,大家还记得Reference Handler线程吗?那个线程会把所有的Reference从pending链表上取出来,然后加入到一个queue中。对于FinalReference,就都会加入到上述代码中的queue中。这个queue中的所有对象,都会被一个名为Finalizer的线程所处理。我们之前也介绍过Reference Handler是2号线程,Finalizer线程是3号线程。Finalizer线程的代码如下所示:

  private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

最终就是会从队列中不断地取出Finalizer对象,然后去调用它的runFinalizer方法。

  private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }

这个方法使用了JavaLangAccess做为参数,其实就是一次虚函数调用,调用到finalizee中的 finalize 方法。

hasBeenFinalized的作用就是保证finalize方法只会被调用一次。

Finalizer vs. Cleaner

因为Finalizer也是一种Reference,所以前边Reference的处理逻辑是和Weak, Soft reference的逻辑十分相似的。

而且Finalizer和Cleaner的作用也十分相似,但有一个巨大的不同在于,finalize方法里可以使object 复活,而 Cleaner 的 clean 方法中不能使得对象复活。

这是因为 finalize 中,可以通过 this 指针访问到 object 对象,例如:

public void finalize() {
    Other.ref = this;
}

这样的话,一个本来应该被回收的对象又在finalize之后复活了。但是Cleaner为什么不行呢?因为它的基类是一个PhantomReference,这个“鬼引用”的 get 方法是这样的:

public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }
    // 其它代码略
}

永远返回null,也就是说对于Cleaner,创建了以后,就再也不能访问它的referent了。

所以现在有人提案,在Java中去掉finalize方法,只使用Cleaner来维护。

其实,这里面还有其他的一些细节,比如为什么Weak的referent会被JVM清掉,而Finalizer又不会清掉自己的referent呢。这些就都不重要了。我就不再讲解了。

我想,经过了这四节课,我们就可以把WeakReference, SoftReference, PhantomReference和Cleaner, FinalReference和Finalizer这6种对象全部搞清楚了。

上一节课:PhantomReference & Cleaner

下一节课:Tracing GC(1)

课程目录:课程目录

编辑于 2019-10-26 00:22