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弱引用 ...
随机推荐
- liunx安装和部署nacos配置中心
1.下载https://github.com/alibaba/nacos/releases nacos-server-1.3.1.tar.gz 源码包2.上传到liunx服务器 /usr/lo ...
- BUUCTF-web HappyCTFd (CVE-2020-7245)
在 CTFd v2.0.0 - v2.2.2 的注册过程中,如果在CTFd的用户名和emails可用,则可以使攻击者接管任意账号. 进入题目,进行注册.查看用户可以看到admin账号,利用漏洞获取ad ...
- 一个edit的学习笔记
https://blog.csdn.net/woshizoe/article/details/51555396
- lua 表
最近在尝试配置 awesome WM,因此粗略地学习了一下 lua . 在学习过程中,我完全被表表在 lua 中的应用所镇住了. 表在 lua 中真的是无处不在:首先,它可以作为字典和数组来用:此外, ...
- MacOS下VUEJS简单入门
微信搜索"艺术行者",关注并回复关键词"vue"获取课程资料 上传的在线学习视频(黑马和传智双元,感谢) VueJs概述与快速入门 学习之前假设你已了解关于 H ...
- 定义 WSGI 接口
# WSGI服务器调用 def application(environ,start_response): start_response('200 OK',[('Content-Type','text/ ...
- Excel绘制经典图表
1.柱形图 2.条形图 3.饼图---复合饼图 4.圆环图 5.组合图 设置不同的纵轴 6.漏斗图 其中:合计呈逐渐下降的趋势,可以用漏斗图进行展示! 绘制漏斗图首先需要构建辅助列,在插入图形的时候选 ...
- PHP zip_entry_close() 函数
定义和用法 zip_entry_close() 函数关闭由 zip_entry_open() 函数打开的 zip 档案.高佣联盟 www.cgewang.com 语法 zip_entry_close( ...
- 字节跳动2020-ByteCamp暑期夏令营研发组习题
概要 本文主要是记录字节2020夏令营的笔试习题,解答部分待日后不断完善. 选择题部分记录不是很完全,剩下没有被收录的大都偏容易. 单项选择题 箱子里有20个红球,18个白球,逐一随机抽取球,问整个过 ...
- RabbitMQ学习总结(2)-API的使用
1. 引用依赖 <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-clie ...