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()方法。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
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引用类型
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!
参考:
Java引用类型之最终引用的更多相关文章
- Java引用类型之软引用(2)
下面接着上一篇介绍第2阶段和第3阶段的处理逻辑. 2.process_phase2() 第2个阶段移除所有的referent还存活的Reference,也就是从refs_list中移除Referenc ...
- Java引用类型之弱引用与幻像引用
这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...
- Java引用类型之软引用(1)
Java使用SoftReference来表示软引用,软引用是用来描述一些“还有用但是非必须”的对象.对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对 ...
- 你不可不知的Java引用类型之——软引用
定义 软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出. 说明 软引用,顾名思义就是比较"软"一点的引用. ...
- 你不可不知的Java引用类型之——虚引用
定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获 ...
- 你不可不知的Java引用类型之——弱引用
定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型.在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收. 说明 弱 ...
- 你不可不知的Java引用类型之——强引用
定义 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器宁愿抛出OOM(OutOfMemoryError)也不会回收它. 说明 不要被这个强字吓到,以为这个引用就很厉害,其实强引用就是程序 ...
- Java的四种引用类型之弱引用
先说结论: 首先,Java中有四种引用类型:强引用.软引用.弱引用.虚引用.-- 在 Java 1.2 中添加的,见 package java.lang.ref; . 其次,这几个概念是与垃圾回收有关 ...
- Java中的四种引用类型,强引用,软引用,弱引用,虚引用
对于Java中的垃圾回收机制来说,对象是否被回收的标准在于该对象是否被引用.因此,引用也是JVM进行内存管理的一个重要概念. Java中对象的引用一般有以下4种类型: 1强引用 2软引用 3弱引用 ...
随机推荐
- 五分钟带你深入了解Redis
相信phper都知道Redis是什么,既然如此,为表仪式感,首先我还是得说说什么是Redis. Redis是什么 redis是一个高性能的key-value数据库,它是完全开源免费的,而且redis是 ...
- 写给程序员的机器学习入门 (八) - 卷积神经网络 (CNN) - 图片分类和验证码识别
这一篇将会介绍卷积神经网络 (CNN),CNN 模型非常适合用来进行图片相关的学习,例如图片分类和验证码识别,也可以配合其他模型实现 OCR. 使用 Python 处理图片 在具体介绍 CNN 之前, ...
- Java实现一个简单的文件上传案例
Java实现一个简单的文件上传案例 实现流程: 1.客户端从硬盘读取文件数据到程序中 2.客户端输出流,写出文件到服务端 3.服务端输出流,读取文件数据到服务端中 4.输出流,写出文件数据到服务器硬盘 ...
- springboot(五)使用FastJson返回Json视图
FastJson简介: fastJson是阿里巴巴旗下的一个开源项目之一,顾名思义它专门用来做快速操作Json的序列化与反序列化的组件.它是目前json解析最快的开源组件没有之一!在这之前jaskJs ...
- 毫无基础的人入门Python,Python入门教程
随着人工智能的发展,Python近两年也是大火,越来越多的人加入到Python学习大军,对于毫无基础的人该如何入门Python呢?这里整理了一些个人经验和Python入门教程供大家参考. 如果你是零基 ...
- scala---lazy
scala中用lazy定义的变量叫做惰性变量,会实现延迟加载.惰性变量只能是不可变的变量.并且只有在调用惰性变量的时候才会被初始化. class Test1 { } object Test1 { de ...
- CentOS yum 安装nginx
当使用以下命令安装Nginx时,发现无法安装成功 yum install -y nginx 需要做一点处理. 安装Nginx源 执行以下命令: rpm -ivh http://nginx.org/pa ...
- JS DOM重点核心笔记
DOM重点核心 文档对象模型,是W3C推荐的处理可扩展的标记语言的标准编程接口 我们主要针对与勇士的操作,主要有创建.增加.删除.修改.查询.属性操作.事件操作 三种动态创建元素的 ...
- js 排他思想案例
<!-- 排他思想 --> <button>按钮1</button> <button>按钮2</button> <button> ...
- 微信公众号里放XLS链接
微信公众号里放XLS链接 我们都知道创建一个微信公众号在公众号中发布一些文章是非常简单的,但公众号添加附件下载的功能却被限制,如今可以使用小程序“微附件”进行在公众号中添加附件,如:xls,word等 ...