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()方法。 

相关文章的链接如下:

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. 题解 洛谷 P4112 【[HEOI2015]最短不公共子串】

    给定两个字符串\(A\)和\(B\),我们需要找出一个串,其在\(A\)中出现且不在\(B\)中出现,这个串为子串或者子序列,求在每种情况下,该串的最短长度. 考虑到后缀自动机可以识别一个字符串的所有 ...

  2. react native redux

    redux可以解决, 程序中所有组件的状态统一管理, 从而使我们可以更加动态的,灵活的控制程序 React:数据管理使用props.stateRedux的主要思想:提供一个数据存储中心,可以供外部访问 ...

  3. 算数组的长度cpp

    今天被自己整傻了.... cpp  int 型的数组就别想用strlen来求长度了,会报错的. (当然java 里直接用length就可以了...) 所以我建议用vector!!!!!!

  4. float对内联元素和块元素的影响

    写在前面:附一篇w3s的关于css float的讲解:http://www.w3school.com.cn/css/css_positioning_floating.asp float属性还没有彻底了 ...

  5. 什么是 PHP 过滤器?

    PHP 过滤器 PHP 过滤器用于验证和过滤来自非安全来源的数据,比如用户的输入. 什么是 PHP 过滤器? PHP 过滤器用于验证和过滤来自非安全来源的数据. 测试.验证和过滤用户输入或自定义数据是 ...

  6. PHP natcasesort() 函数

    定义和用法 natcasesort() 函数用"自然排序"算法对数组进行排序.键值保留它们原始的键名. 在自然排序算法中,数字 2 小于 数字 10.在计算机排序算法中,10 小于 ...

  7. 6.3 省选模拟赛 Decompose 动态dp 树链剖分 set

    LINK:Decompose 看起来很难 实际上也很难 考验选手的dp 树链剖分 矩阵乘法的能力. 容易列出dp方程 暴力dp 期望得分28. 对于链的情况 容易发现dp方程可以转矩阵乘法 然后利用线 ...

  8. System.nanoTime与System.currentTimeMillis比较

    System.nanoTime与System.currentTimeMillis比较 ​currentTimeMillis返回的是系统当前时间和1970-01-01之前间隔时间的毫秒数,如果系统时间固 ...

  9. MyBatis-Plus使用(2)-CRUD接口

    参考文档:https://mybatis.plus/guide/crud-interface.html MyBatis-Plus自带的CRUD方法分为Mapper层和Service层,大多数功能是重叠 ...

  10. centos,linux环境下安装JDK1.8完整

    进入oracle官网下载安装包,cetos一般选择xx-xx-linux-x64.tar.gz.获取到地址后可以点击下载,也可以使用wget命令下载. 在得到下载好的文件后下面就可以开始安装了.比如我 ...