原创

Java引用类型之最终引用

FinalReference类只有一个子类Finalizer,并且Finalizer由关键字final修饰,所以无法继承扩展。类的定义如下:

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

FinalReference是包权限,开发者无法直接进行继承扩展,不过这个类已经有了一个子类Finalizer,如下:

final class Finalizer extends FinalReference { 

    /* A native method that invokes an arbitrary object's finalize method is
       required since the finalize method is protected
     */
    static native void invokeFinalizeMethod(Object o) throws Throwable;

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

    // 定义的这2个属性可将Finalizer对象连接成双向链表
    private Finalizer  next = null,
                       prev = null;

    // 私有构造函数,开发者不能创建Finalizer对象
    private Finalizer(Object finalizee) { // FinalReference指向的对象引用
        super(finalizee, queue);
        add();
    }

    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);  // 封装为Finalizer对象
    }  

    // 将当前对象插入到Finalizer对象链里,新插入的this对象放到双向链表的头部
    // unfinalized是一个静态字段,指向链表的头部,所以如果Finalizer类不卸载,那么这个链表中的对象永远都存活
    private void add() {  // 
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    ...
}

由于构造函数是私有的,所以只能由虚拟机通过调用register()方法将指向的对象封装为Finalizer对象,那么需要清楚知道这个指向的对象以及什么时候调用register()方法。   

在类加载的过程中,如果当前类有重写finalize()方法,则其对象会被封装为FinalReference对象(称为finalizer类),这种类型的对象被回收前会先调用其finalize()方法。所以finalizer类就是指向的对象。

之前在解析类时,在ClassFileParser::parse_method()方法中有如下判断逻辑:

if ( name == vmSymbols::finalize_method_name() &&
       signature == vmSymbols::void_method_signature()) {
    if (m->is_empty_method()) {
      _has_empty_finalizer = true;
    } else {
      _has_finalizer = true;
    }
}

每一个方法都会执行这个判断,如果方法名称为finalize并且返回类型为void时,如果方法体不为空时,_has_finalizer的值才会更新为true。这样最终解析完这个Class文件时会调用如下方法:

void ClassFileParser::set_precomputed_flags(instanceKlassHandle k) {
  Klass* super = k->super();

  // Check if this klass has an empty finalize method (i.e. one with return bytecode only),
  // in which case we don't have to register objects as finalizable
  if (!_has_empty_finalizer) {
    if ( _has_finalizer ||
         (super != NULL && super->has_finalizer())
    ){
      k->set_has_finalizer();
    }
  }

只有当重写的finalize()方法体不为空或者父类就是一个finalizer类型,那么当前的类也是一个finalizer类型。只有finalizer类型才会调用finalize()方法,所以Object类中的finalize()方法不会被调用,因为方法体为空。

接着看什么时候调用register()方法,HotSpot可能会在2个时机中的任意一个调用Finalizer.register()方法来注册对象,这个选择依赖于RegisterFinalizersAtInit这个vm参数是否被设置,默认值为true,也就是在调用构造函数返回之前调用Finalizer.register()方法,如果通过-XX:-RegisterFinalizersAtInit关闭了该参数,那将在对象空间分配好之后将这个对象注册进去。

对于第1个时机,我们在介绍类重写时的Rewriter::rewrite_Object_init()函数时已经介绍过。对于第2个时机,之前介绍过在解析执行时调用的TemplateTable::new()函数,当一个类重写了finalize()方法时,会执行慢速分配,最终会调用instanceKlass::allocate_instance()方法,这在之前也已经介绍过,这里不再介绍。

在HotSpot中,在GC进行可达性分析的时候,如果当前对象是finalizer类型的对象,并且本身不可达(与GC Roots无相连接的引用),则会被加入到一个ReferenceQueue类型的队列中。而系统在初始化的过程中,会启动一个FinalizerThread类型的守护线程(线程名Finalizer),该线程会不断消费ReferenceQueue中的对象,并执行其finalize()方法。对象在执行finalize()方法后,只是断开了与Finalizer的关联,并不意味着会立即被回收,还是要等待下一次GC,而每个对象的finalize()方法都只会执行一次,不会重复执行。


// 从ReferenceQueue中获取对象并执行对象的finalize()方法
private static class FinalizerThread extends Thread {
      ...
      public void run() {
          ...
          final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
          running = true;
          for (;;) {
              try {
                  Finalizer f = (Finalizer)queue.remove(); // 获取可回收对象
                  f.runFinalizer(jla); // 执行对象的finalize()方法
              } catch (InterruptedException x) {  }
          }
      }
}

在之前说到引用线程ReferenceHandler会把pending中保存的等待被回收的对象加入到引用队列,这里就可以从引用队列中获取Finalizer对象,然后调用runFinalizer()方法,实现如下:

private void runFinalizer() {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                invokeFinalizeMethod(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();
}

static native void invokeFinalizeMethod(Object o) throws Throwable;

方法将Finalizer对象从Finalizer对象链里移除出来,这样意味着下次GC发生的时候就可能将其关联的finalizer类型对象回收掉,最后将这个Finalizer对象关联的finalizer类型对象传给了一个native方法invokeFinalizeMethod(),实现如下:

JNIEXPORT void JNICALL
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,jobject ob)
{
    jclass cls;
    jmethodID mid;

    cls = (*env)->GetObjectClass(env, ob);
    if (cls == NULL) return;
    mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
    if (mid == NULL) return;
    (*env)->CallVoidMethod(env, ob, mid);
}

调了这个finalizer类型对象的finalize()方法。 

正文到此结束