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

  1. class FinalReference<T> extends Reference<T> {
  2. public FinalReference(T referent, ReferenceQueue<? super T> q) {
  3. super(referent, q);
  4. }
  5. }

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

  1. final class Finalizer extends FinalReference {
  2.  
  3. /* A native method that invokes an arbitrary object's finalize method is
  4. required since the finalize method is protected
  5. */
  6. static native void invokeFinalizeMethod(Object o) throws Throwable;
  7.  
  8. private static ReferenceQueue queue = new ReferenceQueue();
  9. private static Finalizer unfinalized = null;
  10. private static final Object lock = new Object();
  11.  
  12. // 定义的这2个属性可将Finalizer对象连接成双向链表
  13. private Finalizer next = null,
  14. prev = null;
  15.  
  16. // 私有构造函数,开发者不能创建Finalizer对象
  17. private Finalizer(Object finalizee) { // FinalReference指向的对象引用
  18. super(finalizee, queue);
  19. add();
  20. }
  21.  
  22. /* Invoked by VM */
  23. static void register(Object finalizee) {
  24. new Finalizer(finalizee); // 封装为Finalizer对象
  25. }
  26.  
  27. // 将当前对象插入到Finalizer对象链里,新插入的this对象放到双向链表的头部
  28. // unfinalized是一个静态字段,指向链表的头部,所以如果Finalizer类不卸载,那么这个链表中的对象永远都存活
  29. private void add() { //
  30. synchronized (lock) {
  31. if (unfinalized != null) {
  32. this.next = unfinalized;
  33. unfinalized.prev = this;
  34. }
  35. unfinalized = this;
  36. }
  37. }
  38.  
  39. ...
  40. }

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

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

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

  1. if ( name == vmSymbols::finalize_method_name() &&
  2. signature == vmSymbols::void_method_signature()) {
  3. if (m->is_empty_method()) {
  4. _has_empty_finalizer = true;
  5. } else {
  6. _has_finalizer = true;
  7. }
  8. }

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

  1. void ClassFileParser::set_precomputed_flags(instanceKlassHandle k) {
  2. Klass* super = k->super();
  3.  
  4. // Check if this klass has an empty finalize method (i.e. one with return bytecode only),
  5. // in which case we don't have to register objects as finalizable
  6. if (!_has_empty_finalizer) {
  7. if ( _has_finalizer ||
  8. (super != NULL && super->has_finalizer())
  9. ){
  10. k->set_has_finalizer();
  11. }
  12. }

只有当重写的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()方法都只会执行一次,不会重复执行。

  1. // 从ReferenceQueue中获取对象并执行对象的finalize()方法
  2. private static class FinalizerThread extends Thread {
  3. ...
  4. public void run() {
  5. ...
  6. final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
  7. running = true;
  8. for (;;) {
  9. try {
  10. Finalizer f = (Finalizer)queue.remove(); // 获取可回收对象
  11. f.runFinalizer(jla); // 执行对象的finalize()方法
  12. } catch (InterruptedException x) { }
  13. }
  14. }
  15. }

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

  1. private void runFinalizer() {
  2. synchronized (this) {
  3. if (hasBeenFinalized()) return;
  4. remove();
  5. }
  6. try {
  7. Object finalizee = this.get();
  8. if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
  9. invokeFinalizeMethod(finalizee);
  10. /* Clear stack slot containing this variable, to decrease
  11. the chances of false retention with a conservative GC */
  12. finalizee = null;
  13. }
  14. } catch (Throwable x) { }
  15. super.clear();
  16. }
  17.  
  18. static native void invokeFinalizeMethod(Object o) throws Throwable;

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

  1. JNIEXPORT void JNICALL
  2. Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,jobject ob)
  3. {
  4. jclass cls;
  5. jmethodID mid;
  6.  
  7. cls = (*env)->GetObjectClass(env, ob);
  8. if (cls == NULL) return;
  9. mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
  10. if (mid == NULL) return;
  11. (*env)->CallVoidMethod(env, ob, mid);
  12. }

调了这个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引用类型之最终引用的更多相关文章

  1. Java引用类型之软引用(2)

    下面接着上一篇介绍第2阶段和第3阶段的处理逻辑. 2.process_phase2() 第2个阶段移除所有的referent还存活的Reference,也就是从refs_list中移除Referenc ...

  2. Java引用类型之弱引用与幻像引用

    这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...

  3. Java引用类型之软引用(1)

    Java使用SoftReference来表示软引用,软引用是用来描述一些“还有用但是非必须”的对象.对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对 ...

  4. 你不可不知的Java引用类型之——软引用

    定义 软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出. 说明 软引用,顾名思义就是比较"软"一点的引用. ...

  5. 你不可不知的Java引用类型之——虚引用

    定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获 ...

  6. 你不可不知的Java引用类型之——弱引用

    定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型.在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收. 说明 弱 ...

  7. 你不可不知的Java引用类型之——强引用

    定义 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器宁愿抛出OOM(OutOfMemoryError)也不会回收它. 说明 不要被这个强字吓到,以为这个引用就很厉害,其实强引用就是程序 ...

  8. Java的四种引用类型之弱引用

    先说结论: 首先,Java中有四种引用类型:强引用.软引用.弱引用.虚引用.-- 在 Java 1.2 中添加的,见 package java.lang.ref; . 其次,这几个概念是与垃圾回收有关 ...

  9. Java中的四种引用类型,强引用,软引用,弱引用,虚引用

    对于Java中的垃圾回收机制来说,对象是否被回收的标准在于该对象是否被引用.因此,引用也是JVM进行内存管理的一个重要概念. Java中对象的引用一般有以下4种类型: 1强引用  2软引用  3弱引用 ...

随机推荐

  1. set自动排序去重 stringstream流分割字符

    链接:https://vjudge.net/problem/UVA-10815#author=0 题意:给几段句子,按字典序筛选出单词. 题解:用C的话太麻烦,不如用自动去重并排序的set容器.有个地 ...

  2. 今天完成顺利编译d:\test\Console.java

    今天完成顺利编译d:\test\Console.java import javax.swing.*;import java.awt.event.*; public class Console{ pub ...

  3. jmeter控制器入门笔记一

    @@@@@@@@@@@@@@@ 千里之行 今天记录一下个人才使用控制器时的一些心得.逻辑控制器在jmeter中有很多种,个人根据官方解释理解的作用就是:通过控制器可以更好地控制请求的执行顺序.jmet ...

  4. Spring学习之Spring中AOP方式切入声明式事务

    mybatis-spring官方文档说明 一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中.而不是给 MyBatis 创建一个新的 ...

  5. Django学习路34_models 文件创建数据表

    from django.db import models # Create your models here. class BookInfoManager(models.Manager): '''图书 ...

  6. Python List insert()方法

    描述 insert() 函数用于将指定对象插入列表的指定位置.高佣联盟 www.cgewang.com 语法 insert()方法语法: list.insert(index, obj) 参数 inde ...

  7. Pr剪辑

    目录 Pr剪辑教程 入门基础 创建序列类别 处理非正常序列 导出文件 导出设置 导入各类别素材 简单使用: 剪辑素材常用方法 剃刀工具 选择工具 波纹编辑工具 打入点和出点 剪辑速度 整个素材视频速度 ...

  8. 关于c/c++指针,指针的指针

    伪军迷祝:建军节快乐! 当调用一个函数时,实际上入参使用的都是副本(除非是引用),指针也不例外.举个例子如: void func(int a, int * p); 当调用func时无论是a还是p其实传 ...

  9. Python解决网吧收费系统,远控网吧电脑设备!

    python破解网吧收费系统,远控网吧电脑设备! 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更 ...

  10. Javascript注意点

    Javascript注意点 在img标签中的src如果为相对路径, 但是在js获取的时候会转为全路径 候选框中, 在执行onclick之前, 会由于html的特征先设置checked属性 为a标签添加 ...