Java引用类型之最终引用

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

class FinalReference extends Reference {
    public FinalReference(T referent, ReferenceQueue 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()方法。 

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码 

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程 

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)  

7、HotSpot的类模型(3) 

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)  

10、HotSpot的对象模型(6) 

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器 

14、类的双亲委派机制 

15、核心类的预装载

16、Java主类的装载  

17、触发类的装载  

18、类文件介绍 

19、文件流 

20、解析Class文件 

21、常量池解析(1) 

22、常量池解析(2)

23、字段解析(1)

24、字段解析之伪共享(2) 

25、字段解析(3)  

26、字段解析之OopMapBlock(4)

27、方法解析之Method与ConstMethod介绍  

28、方法解析

29、klassVtable与klassItable类的介绍  

30、计算vtable的大小 

31、计算itable的大小 

32、解析Class文件之创建InstanceKlass对象 

33、字段解析之字段注入 

34、类的连接  

35、类的连接之验证 

36、类的连接之重写(1) 

37、类的连接之重写(2)

38、方法的连接  

39、初始化vtable 

40、初始化itable  

41、类的初始化 

42、对象的创建  

43、Java引用类型 

44、Java引用类型之软引用(1)

45、Java引用类型之软引用(2)

46、Java引用类型之弱引用与幻像引用  

47、Java引用类型之最终引用 

作者持续维护的个人博客  classloading.com

关注公众号,有HotSpot源码剖析系列文章!

   

参考:

(1)深入理解JDK中的Reference原理和源码实现

(2)JVM源码分析之FinalReference完全解读

 

 

 

你可能感兴趣的:(Java引用类型之最终引用)