目录

  • 零、前情概要

    • ref包内容
    • 系列目录
    • 上一章回顾
  • 一、FinalReference
  • 二、Finalizer
    • 源码概览
    • FinalizerThread
  • 三、register
    • -XX:+RegisterFinalizersAtInit
    • new指令
    • 访问标识JVM_ACC_HAS_FINALIZER
    • invokeSpecial指令
  • 四、finalization机制
  • 五、Runtime.runFinalization()
  • 六、过时的finalize方法
  • 总结

零、前情概要

1.java.lang.ref包的内容

  • Reference & ReferenceQueue & ReferenceHandler

    • SoftReference & WeakReference
    • PhantomReference
      • jdk.internal.ref.Cleaner
    • FinalReference
      • Finalizer & FinalizerThread
  • java.lang.ref.Cleaner
  • # 其中会涉及两个虚拟机线程:ReferenceHandler & FinalizerThread

2.系列目录

  • 《ref包简述》
  • 《Reference & ReferenceQueue & ReferenceHandler》
  • 《SoftReference & WeakReference》
  • 《PhantomReference & jdk.internal.ref.Cleaner》
  • 《FinalReference & Finalizer & FinalizerThread》
  • 《当WeakReference的referent重写了finalize方法时会发生什么》
  • 《java.lang.ref.Cleaner》

3.上一章回顾

  • 理解虚引用PhantomReference的适用场景
  • 理解Java引用机制的“优雅”和“提前规避”的含义
  • 参看jdk.internal.ref.Cleaner的源码辅助验证Java引用机制的“优雅”和“提前规避”的实现
  • 参看java.nio.DirectByteBuffer的构造函数和静态内部类Deallocator中是如何使用Cleaner来释放native memory
    • 理解资源释放逻辑Runnable thunk和引用jdk.internal.ref.Cleaner分离(非强绑定)带来通用的优点
  • 理解虚引用的流程和为什么不让你使用Cleaner
  • 理解Cleaner中双线链表的存在:看似冗余,实则必须
  • 理解为什么不让你使用Cleaner

一、FinalReference

FinalReference是package的访问权限,因此和jdk.internal.ref.Cleaner一样,都是JDK内部调用,不允许Java程序员直接调用。所以在第一章谈java.lang.ref包规范的时候,文档里面就只给了SoftReference、WeakReference、PhantomReference三个Java引用类。

FinalReference没有API documentation,甚至连注释都几乎省略了。简而言之,FinalReference类表面上没什么能直接探索的,看看源码吧。

package java.lang.ref;/*** Final references, used to implement finalization*/
class FinalReference<T> extends Reference<T> {public FinalReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}@Overridepublic boolean enqueue() {throw new InternalError("should never reach here");}
}

看了源码,好家伙,你可以直呼好家伙。

实在是太简洁了,万幸还是能看出一点东西:

  • 构造函数:和虚引用一样,必须传入ReferenceQueue;
  • enqueue:不允许直接调用FinalReference.enqueue,如果想让FinalReference对象进队列,只能通过构造函数传入的ReferenceQueue。当然,通过代码开发者已经非常直接、非常明确的告诉你,进什么队列呀,别进啦,就不是给你用的;
  • 唯一的类注释Final references, used to implement finalization
    • FinalReference,用来实现finalization机制

问题来了,finalization机制是个什么玩意儿?

得嘞,没有API documentation、几乎没有可用的注释、几乎看不出什么端倪的源码,那只剩一条路了,看看继承关系吧。

也是非常简洁明确,转去探索Finalizer吧。


二、Finalizer

子类Finalizer和FinalReference一样,都是package权限,没有API documentation。但相较之,Finalizer的源码和相关注释透露的信息那可算是太丰富了。

1.源码概览

java.lang.ref.Finalizer

  • 属性

    • queue:关联的ReferenceQueue
    • unfinalized、next、prev:在Finalizer中维护一条双向链表,为了保证Java引用机制的正常运行,原因参看上一章《PhantomReference & jdk.internal.ref.Cleaner》
    • lock:finalization机制本质是一个生产者消费者模型,虽然仓库是Finalizer关联的引用队列,但是当操作仓库的同时也需要操作这条双向链表,所以就需要lock当操作链表的监视器
  • 构造函数:Finalizer(Object finalizee),必须传入一个ReferenceQueue
  • 方法
    • register:这个方法需要深入hotspot,查看是哪里调用了该方法将对象注册成一个FinalReference
    • runFinalizer:FinalizerThread线程的核心逻辑
    • Runtime.runFinalization()
      • runFinalization
      • forkSecondaryFinalizer
      • 实现并不复杂,这一块在文末作为补充讨论
  • 静态内部类:java.lang.ref.Finalizer.FinalizerThread
  • 静态代码块:建立并启动FinalizerThread线程

属性一个引用队列、一个双向链表、一个监视器,需要关注的是为什么需要这条链表,在上一章谈jdk.internal.ref.Cleaner的时候分析过了,是为了保证Java引用机制的正常运行,所以需要通过这条双向链表来保持Reference的强可达状态。

构造方法

    private Finalizer(Object finalizee) {super(finalizee, queue);// push onto unfinalizedsynchronized (lock) {if (unfinalized != null) {this.next = unfinalized;unfinalized.prev = this;}unfinalized = this;}}

因为Finalizer继承FinalReference,所以必须super(referent,queue)。紧接着在构造函数中将新建的Finalizer对象加入到双向链表。

对比jdk.internal.ref.Cleaner的代码实现,感觉这两个类是不同的人写的,Cleaner好歹add、remove方法走起来嘛,Finalizer直接将操作双向链表的增删操作分散在构造函数和runFinalizer方法中了。

静态代码块

    static {ThreadGroup tg = Thread.currentThread().getThreadGroup();for (ThreadGroup tgn = tg;tgn != null;tg = tgn, tgn = tg.getParent());Thread finalizer = new FinalizerThread(tg);finalizer.setPriority(Thread.MAX_PRIORITY - 2);finalizer.setDaemon(true);finalizer.start();}

在Finalizer类加载的类初始化阶段,静态代码块的代码就会执行,在静态代码块中启动了一个优先级为Thread.MAX_PRIORITY - 2的后台线程FinalizerThread。


2.FinalizerThread

FinalizerThread:

    private static class FinalizerThread extends Thread {private volatile boolean running;FinalizerThread(ThreadGroup g) {super(g, null, "Finalizer", 0, false);}public void run() {// in case of recursive call to run()if (running)return;// Finalizer thread starts before System.initializeSystemClass// is called.  Wait until JavaLangAccess is availablewhile (VM.initLevel() == 0) {// delay until VM completes initializationtry {VM.awaitInitLevel(1);} catch (InterruptedException x) {// ignore and continue}}final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();running = true;for (;;) {try {Finalizer f = (Finalizer)queue.remove();f.runFinalizer(jla);} catch (InterruptedException x) {// ignore and continue // 因为remove中会通过object.wait实现进程间通信,而wait是允许中断的,所以这里捕获中断异常// 又因为finalization机制并不是给用户使用的,是JDK内部垃圾清理的兜底机制,所以不需要将异常信息抛给用户}}}}

runFinalizer:

    private void runFinalizer(JavaLangAccess jla) {// 同步操作链表,将Finalizer从双向链表上断下来synchronized (lock) {if (this.next == this)      // already finalizedreturn;// unlink from unfinalizedif (unfinalized == this)unfinalized = this.next;elsethis.prev.next = this.next;if (this.next != null)this.next.prev = this.prev;this.prev = null;this.next = this;           // mark as finalized}try {// 拿到referentObject finalizee = this.get();if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {// 调用referent.finalize()方法jla.invokeFinalize(finalizee);// Clear stack slot containing this variable, to decrease// the chances of false retention with a conservative GC// 断开referent和Finalizer之间的引用,使得referent变成真正的不可达状态,在下一次GC的时候,就能够释放referent的资源// 第一次GC的时候只是将Finalizer挂到pending-reference list上,然后reference-handler线程将其移入引用队列// 紧接着FinalizerThread从引用队列拿到Finalizer,并调用其finalize方法,最后断开referent和Finalizer之间的引用finalizee = null;}} catch (Throwable x) { }super.clear();}

invokeFinalize:

            @SuppressWarnings("deprecation")public void invokeFinalize(Object o) throws Throwable {o.finalize();}

在Finalizer类被加载的阶段,会通过静态代码块建立并运行FinalizerThread线程。

FinalizerThread中有一个boolean running属性,是为了防止递归调用线程的run方法(in case of recursive call to run())。

整个线程的核心逻辑:

  • 从Finalizer类关联的引用队列中remove节点(引用队列的remove方法和enqueue方法可以线程间通信);
  • 拿到Finalizer对象,调用其runFinalizer方法;
  • 在runFinalizer方法中实现了FinalizerThread线程的主要逻辑:
    • 先操作Finalizer的双向链表:和jdk.internal.ref.Cleaner的双向链表逻辑一样,先判断是否已经删除过,然后处理头节点指针,再断开节点的双向链接
    • 通过Reference.get拿到referent
      • 还记得前面SoftReference、WeakReference、PhantomReference、jdk.internal.ref.Cleaner吗,这几个引用在被GC挂到pending-reference list上的时候会clear referent,即referent=null,在第一章《ref包简述》谈包规范的时候API documentation中就提到了
      • 但是FinalReference/Finalizer不会clear referent,这里你可以去看hotspot的源码,也可以回看第二章《Reference & ReferenceQueue & ReferenceHandler》和Reference.referent的源码注释,里面特别提到了FinalReference的例外
      • 源码注释:FinalReference (which exists to support finalization) differs from other references, because a FinalReference is not cleared when notified.
    • 调用referent.finalize()方法
      • 整个过程都在一个try catch代码块中,catch的是Throwable,并且是空代码块,这意味着无论是错误还是异常都会被捕获,但是不会给调用方/用户任何提示信息
    • 在调用了referent.finalize()方法之后,最后断开Finalizer和referent之间的引用关系,这样使得referent变成不可达状态,最终在下一次GC的时候referent才会被回收
      • 也就是意味着Finalizer/FinalReference封装的referent至少会经历两次GC才会被回收资源:第一次GC的时候,GC只是将Finalizer放入关联的引用队列,然后FinalizerThread经引用队列拿到并调用referent的finalize方法,最后断开Finalizer和referent之间的引用,使得referent变成真正的不可达;当referent变成不可达状态的时候,第二次GC才会释放referent的资源
      • 回过头来再看《深入理解Java虚拟机:JVM高级特性与最佳实践》一书,在3.2.4章节谈对象“生存还是死亡”和finalize逃逸,就更容易理解,有对的地方也有不太准确的地方:被Finalizer封装的referent失去强引用之后确实是要经过两次标记才会被回收,实际上是经历两次GC,而不是书中所说的在Finalizer关联的引用队中做标记

截图来自《深入理解Java虚拟机:JVM高级特性与最佳实践》3.2.4章节:

FinalizerThread线程的逻辑还是很简单的,Finalizer是一个Reference,所以遵守Java引用机制的流程。

结合Java引用机制,从Finalizer和FinalizerThread的源码可知,finalization机制也是一个生产者消费者模型:

  • 生产者:reference-handler线程
  • 产品:Finalizer对象
  • 仓库:Finalizer类中的static queue,即引用队列ReferenceQueue
    • 注意这里不是Finalizer中的双向队列,双向队列是因为Finalizer需要强引用来保证Java引用机制的正常运行
    • 在FinalizerThread中是通过queue.remove拿到Finalizer,所以仓库是Finalizer对象关联的ReferenceQueue
  • 消费者:FinalizerThread线程

由当前可知的信息总结一下Finalizer的流程:

  • 当Finalizer的referent失去强引用后,GC时发现referent的可达性发生变化,就会将Finalizer挂到pending-reference list;
  • 随后reference-handler线程从pending-reference list取走Finalizer对象,并将其放入关联的引用队列;
  • 接着FinalizerThread通过引用队列拿到Finalizer封装的referent并调用其finalize方法;
    • 其中finalize方法是我们自定义的资源释放逻辑,需要重写java.lang.Object.finalize()方法
  • 最后,断开Finalizer和referent之间的引用关系,使referent变成真正的不可达状态

看起来似乎弄明白了finalization机制。

但是你仔细想一下,要在哪里创建FinalReference/Finalizer对象,好接入Java引用机制呢?

回想一下其他几种Java引用是怎么使用:

  • SoftReference、WeakReference的使用,是需要我们建立相关的实例,即new SoftReference(referent,queue),这样手动将referent接入Java引用流程
  • PhantomReference不建议也不应该直接使用,而是要像JDK内部jdk.internal.ref.Cleaner的实现一样:新建一个子类继承PhantomReference(子类中实现双向链表,创建一个消费者线程),并传入规范化自定义的资源清理的逻辑(要清理的资源的元信息需要从referent中copy一份保存到清理逻辑中,因为这些资源一般是native memory、文件句柄等不是GC能够自动回收的资源,并且要保证资源释放的元信息不能随着referent的消亡而消失),等到referent要被回收之后,自定义的消费者线程从关联队列中拿继承PhantomReference的子类,并调用清理逻辑;
  • Finalizer/FinalReference呢?

Finalizer/FinalReference是package规范,你用不了,那虚拟机是在哪里将referent封装成Finalizer/FinalReference的?所有对象都会直接或间接地继承java.lang.Object,都有finalize方法,那所有对象都会被封装到Finalizer/FinalReference中吗?

Question什么样的对象在什么时候会被虚拟机封装成Finalizer?

要知道答案,需要追踪一个方法:Finalizer.register(referent)

该方法的注释:invoked by VM,要想知道哪些对象在哪里会被封装成Finalizer,首先要找到虚拟机在哪里调用register方法。


三、Finalizer.register

从上面Finalizer源码可知finalization机制的下半部分,但是上半部分虚拟机在哪里将对象封装成Finalizer/FinalReference,我们还需要追踪Finalizer.register方法。

1.-XX:+RegisterFinalizersAtInit

先看一个虚拟机参数-XX:+RegisterFinalizersAtInit:

  • 默认值为true,表示实例化对象在调用构造函数返回时调用Finalizer.register方法;

    • 当RegisterFinalizersAtInit=true时,类链接阶段就会将java.lang.Object构造函数中的_return指令重写成_return_register_finalizer指令;
  • 如果通过-XX:-RegisterFinalizersAtInit关闭了该参数,即值为false,表示实例化对象在分配内存空间时调用Finalizer.register方法;

这表明jvm调用finalizer.register方法和对象创建有关系。

通过javap反编译对象创建:

Object object = new Object();

对应的字节码:

  • new指令主要是为新建的对象分配内存、默认初始化并设置对象头,new指令执行完对虚拟机来说this就存在了;
  • invokespecial指令用于调用对象的构造函数;
  • 最后将创建好的对象写回:
    • 如果新建的对象是一个局部变量那会astore_x存放到方法的局部变量表;
    • 如果是一个实例属性那会putfiled写回到对象在堆内存中的实例数据域;
    • 如果是一个静态属性那会putstatic写到该类的方法区。

当然,我们要关注的是new和invokespecial指令。所以现在有两个探索思路:一个是先找hotspot源码中哪里用到了参数RegisterFinalizersAtInit,另一个是查看new指令和invokespecial指令对应的虚拟机代码实现。第一个能更快定位到关键点,第二个要慢慢查看源码逻辑。

正如RegisterFinalizersAtInit参数有两个值两种结果,你在hotspot源码中也能找到两个地方使用了该参数:

  • 一处是instanceKlass::allocate_instance:创建对象分配内存的时候
  • 另一处是类链接时重写Rewriter::rewrite_bytecodes:解析class文件中的方法时重写构造函数的return字节码

2.new指令

地址:jdk/src/hotspot/share/interpreter/interpreterRuntime.cpp

JRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* current, ConstantPool* pool, int index))Klass* k = pool->klass_at(index, CHECK);InstanceKlass* klass = InstanceKlass::cast(k);// Make sure we are not instantiating an abstract klassklass->check_valid_for_instantiation(true, CHECK);// Make sure klass is initializedklass->initialize(CHECK);// At this point the class may not be fully initialized// because of recursive initialization. If it is fully// initialized & has_finalized is not set, we rewrite// it into its fast version (Note: no locking is needed// here since this is an atomic byte write and can be// done more than once).//// Note: In case of classes with has_finalized we don't//       rewrite since that saves us an extra check in//       the fast version which then would call the//       slow version anyway (and do a call back into//       Java).//       If we have a breakpoint, then we don't rewrite//       because the _breakpoint bytecode would be lost.oop obj = klass->allocate_instance(CHECK);current->set_vm_result(obj);
JRT_END

关注klass->allocate_instance方法,该方法的实现:

地址:jdk/src/hotspot/share/oops/instanceKlass.cpp

instanceOop InstanceKlass::allocate_instance(TRAPS) {bool has_finalizer_flag = has_finalizer(); // Query before possible GC// 获取该对象需要的空间大小int size = size_helper();  // Query before forming handle.instanceOop i;// 分配内存i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);// 这里判断是否要将对象封装成一个Finalizerif (has_finalizer_flag && !RegisterFinalizersAtInit) {i = register_finalizer(i, CHECK_NULL);}return i;
}

会先通过has_finalizer()方法查询该类有没有JVM_ACC_HAS_FINALIZER标识,如果有该标志并且RegisterFinalizersAtInit值为false(即-XX:-RegisterFinalizersAtInit),那么就调用register_finalizer方法,register_finalizer方法的实现如下:

instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {if (TraceFinalizerRegistration) {tty->print("Registered ");i->print_value_on(tty);tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", p2i(i));}instanceHandle h_i(THREAD, i);// Pass the handle as argument, JavaCalls::call expects oop as jobjectsJavaValue result(T_VOID);JavaCallArguments args(h_i);methodHandle mh (THREAD, Universe::finalizer_register_method());JavaCalls::call(&result, mh, &args, CHECK_NULL);return h_i();
}

在register_finalizer方法中就会调用java.lang.ref.Finalizer.register(referent)将该对象封装成一个Finalizer实例

小结

new指令追查小结

  • new指令逻辑在JVM中的InterpreterRuntime::_new方法中实现
  • InterpreterRuntime::_new方法中会调用InstanceKlass::allocate_instance方法为实例化的对象分配内存
  • InstanceKlass::allocate_instance方法中:
    • 如果参数RegisterFinalizersAtInit=false
    • 并且该类有JVM_ACC_HAS_FINALIZER标志
    • 那么就调用InstanceKlass::register_finalizer方法
  • InstanceKlass::register_finalizer方法中会调用java.lang.ref.Finalizer.register(referent)方法将新建的对象封装成一个Finalizer,自身作为referent
  • 结论:如果RegisterFinalizersAtInit=false并且该类被标记为JVM_ACC_HAS_FINALIZER类,那么创建对象分配内存时就会调用java.lang.ref.Finalizer.register(referent)将新建对象封装为一个Finalizer,自身作为referent

3.JVM_ACC_HAS_FINALIZER

还有一个问题,JVM_ACC_HAS_FINALIZER标志是在什么时候、什么条件下设置的,又是什么含义?

此前通过has_finalizer()方法查询该类有没有JVM_ACC_HAS_FINALIZER标识,最终追下来是在类加载的解析class文件(也就是类加载的java.lang.ClassLoader.defineClass方法中会做的一个step)ClassFileParser::parse_method()方法中会做相关判断:

地址:jdk/src/hotspot/share/classfile/classFileParser.cpp

Method* ClassFileParser::parse_method(const ClassFileStream* const cfs,bool is_interface,const ConstantPool* cp,AccessFlags* const promoted_flags,TRAPS) {// 代码太多,省略if (name == vmSymbols::finalize_method_name() &&signature == vmSymbols::void_method_signature()) {if (m->is_empty_method()) {_has_empty_finalizer = true;} else {_has_finalizer = true;}}// 代码省略
}

在解析类文件的方法时,每个方法都会执行parse_method,如果方法名是finalize、返回值为void并且方法体不为空(简而言之就是重写了java.lang.Object.finalze()方法),那么就设置_has_finalizer = true。

最后在解析完class文件的时候,如果该类或者其父类设置_has_finalizer = true,在ClassFileParser::set_precomputed_flags方法中会给该类设置JVM_ACC_HAS_FINALIZER的标志,该方法实现如下:

void ClassFileParser::set_precomputed_flags(InstanceKlass* ik) {assert(ik != NULL, "invariant");const Klass* const super = ik->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 finalizableif (!_has_empty_finalizer) {if (_has_finalizer ||(super != NULL && super->has_finalizer())) {ik->set_has_finalizer();}}

ClassFileParser::set_precomputed_flags方法中,InstanceKlass->set_has_finalizer实际上是其父类的方法Klass->set_has_finalizer,地址jdk/klass.hpp at master · openjdk/jdk · GitHub:

void set_access_flags(AccessFlags flags) { _access_flags = flags; }
void set_has_finalizer()              { _access_flags.set_has_finalizer(); }

其中_access_flags实际上是AccessFlags类,转到该类中,地址jdk/accessFlags.hpp at master · openjdk/jdk · GitHub:

void set_has_finalizer()             { atomic_set_bits(JVM_ACC_HAS_FINALIZER);           }

通过has_finalizer()的探索可知:在类加载时会解析类文件,在解析类文件中的方法时,如果一个类重写了java.lang.Object.finalize()方法(方法名finalize,返回值void,方法体非空),那么该类AccessFlags中的JVM_ACC_HAS_FINALIZER比特位就会被置位为1,表示这是一个finalizer类。

AccessFlags实际是一个jint类型,设置JVM_ACC_HAS_FINALIZER就是在对应的比特位上置一,源码地址jdk/accessFlags.hpp at master · openjdk/jdk · GitHub

以上是从JVM层面来的追查,其实在Java层面你可以窥见端倪,在sun.jvm.hotspot.oops.AccessFlags类中会判断klass flags:

其中,JVM_ACC_HAS_FINALIZERsun.jvm.hotspot.runtime.ClassConstants接口中定义:

小结

JVM_ACC_HAS_FINALIZER标志追查小结:

  • 在类加载的时候,会解析class文件
  • 通过ClassFileParser::parse_method()方法解析类文件中每一个method时:
    • 如果方法名是finalize
    • 并且返回值是void
    • 并且方法体非空
    • 那么就设置_has_finalizer=true
  • 在类文件解析结束之前,会调用ClassFileParser::set_precomputed_flags方法做判断,如果该类或其父类设置了_has_finalizer=true:
    • 那么就会调用Klass->set_has_finalizer,该方法实际上是调用AccessFlags->set_has_finalizer
    • 在AccessFlags->set_has_finalizer中,会设置相关比特位atomic_set_bits(JVM_ACC_HAS_FINALIZER)
  • 结论:如果一个类重写了java.lang.Object.finalize()方法(方法名为finalize,返回值为void,方法体非空),那么在加载该类解析类文件的时候,该类就会被设置JVM_ACC_HAS_FINALIZER访问标识

4.invokespecial指令

如前所述,RegisterFinalizersAtInit参数有两个地方使用到,还有一处是重写该类的构造函数的return字节码,函数Rewriter::rewrite_bytecodes:

地址:jdk/src/hotspot/share/interpreter/rewriter.cpp

void Rewriter::rewrite_bytecodes(TRAPS) {assert(_pool->cache() == NULL, "constant pool cache must not be set yet");// determine index maps for Method* rewritingcompute_index_maps();// 用到了RegisterFinalizersAtInit参数if (RegisterFinalizersAtInit && _klass->name() == vmSymbols::java_lang_Object()) {bool did_rewrite = false;int i = _methods->length();while (i-- > 0) {Method* method = _methods->at(i);if (method->intrinsic_id() == vmIntrinsics::_Object_init) {// rewrite the return bytecodes of Object.<init> to register the// object for finalization if needed.methodHandle m(THREAD, method);//注意该函数会重写invokespecial的return指令rewrite_Object_init(m, CHECK);did_rewrite = true;break;}}assert(did_rewrite, "must find Object::<init> to rewrite it");}// 省略
}

在Rewriter::rewrite_bytecodes方法中,如果RegisterFinalizersAtInit=true、类名是java.lang.Object,内置方法是Object的都构造函数(Intrinsic methods),那么就调用Rewriter::rewrite_Object_init方法。

注意该方法中的一句注释:必要情况下,重写Object构造函数的return指令将对象注册为Finalizer。

rewrite the return bytecodes of Object.<init> to register the object for finalization if needed.

继续追看Rewriter::rewrite_Object_init方法:

// The new finalization semantics says that registration of
// finalizable objects must be performed on successful return from the
// Object.<init> constructor.  We could implement this trivially if
// <init> were never rewritten but since JVMTI allows this to occur, a
// more complicated solution is required.  A special return bytecode
// is used only by Object.<init> to signal the finalization
// registration point.  Additionally local 0 must be preserved so it's
// available to pass to the registration function.  For simplicity we
// require that local 0 is never overwritten so it's available as an
// argument for registration.void Rewriter::rewrite_Object_init(const methodHandle& method, TRAPS) {RawBytecodeStream bcs(method);while (!bcs.is_last_bytecode()) {Bytecodes::Code opcode = bcs.raw_next();switch (opcode) {// 在这里将return字节码重写成_return_register_finalizercase Bytecodes::_return: *bcs.bcp() = Bytecodes::_return_register_finalizer; break;case Bytecodes::_istore:case Bytecodes::_lstore:case Bytecodes::_fstore:case Bytecodes::_dstore:case Bytecodes::_astore:if (bcs.get_index() != 0) continue;// fall throughcase Bytecodes::_istore_0:case Bytecodes::_lstore_0:case Bytecodes::_fstore_0:case Bytecodes::_dstore_0:case Bytecodes::_astore_0:THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(),"can't overwrite local 0 in Object.<init>");break;default:break;}}
}

在该方法中,会将Object构造函数中的Bytecodes::_return字节码指令重写成Bytecodes::_return_register_finalizer字节码指令,关注一下该方法的注释:

A special return bytecode is used only by Object.<init> to signal the finalization registration point.

查看Bytecodes::_return_register_finalizer指令的定义:

地址:jdk/src/hotspot/share/interpreter/bytecodes.cpp

def(_return_register_finalizer , "return_register_finalizer" , "b"    , NULL    , T_VOID   ,  0, true, _return);

_return_register_finalizer指令在JVM中实际上是return_register_finalizer,最终在解释器中的实现逻辑:

if (_desc->bytecode() == Bytecodes::_return_register_finalizer) {assert(state == vtos, "only valid state");__ movptr(c_rarg1, aaddress(0));__ load_klass(rdi, c_rarg1);__ movl(rdi, Address(rdi, Klass::access_flags_offset()));__ testl(rdi, JVM_ACC_HAS_FINALIZER);Label skip_register_finalizer;__ jcc(Assembler::zero, skip_register_finalizer);__ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::register_finalizer), c_rarg1);__ bind(skip_register_finalizer);}

_return_register_finalizer字节码的实现逻辑:

  • 拿到该对象的内存地址,保存在c_rarg1中
  • 将该地址加载到rdi寄存器
  • 然后通过rdi拿到该类访问标识access_flag的偏移地址并保存到rdi中
  • 测试该类的访问标识access_flag中JVM_ACC_HAS_FINALIZER比特位是否置位
  • 如果对应的比特位为0即没有置位,那么跳过register_finalizer
  • 否则,调用InterpreterRuntime::register_finalizer

转到InterpreterRuntime::register_finalizer方法:

地址:jdk/interpreterRuntime.cpp at master · openjdk/jdk · GitHub

JRT_ENTRY(void, InterpreterRuntime::register_finalizer(JavaThread* current, oopDesc* obj))assert(oopDesc::is_oop(obj), "must be a valid oop");assert(obj->klass()->has_finalizer(), "shouldn't be here otherwise");InstanceKlass::register_finalizer(instanceOop(obj), CHECK);
JRT_END

在InterpreterRuntime::register_finalizer中,最终又回到InstanceKlass::register_finalizer方法中,这个方法在上文探索new指令时已经分析过了,InstanceKlass::register_finalizer方法中会调用java.lang.ref.Finalizer.register(referent)将该对象封装成一个Finalizer实例。

小结

invokespecial指令相关探索:

  • hotspot在类链接阶段会重写部分字节码指令(例如本例中的重写了Object构造函数的return指令)
  • 发现RegisterFinalizersAtInit参数也在Rewriter::rewrite_bytecodes方法中使用到了
  • 在Rewriter::rewrite_bytecodes方法中:
    • 如果RegisterFinalizersAtInit=true
    • 并且类名是java.lang.Object
    • 方法是内置方法且是Object的构造函数
    • 那么就调用rewrite_Object_init方法重写java.lang.Object构造函数中的return字节码指令
  • 在hotspot的rewrite_Object_init方法中,将Bytecodes::_return重写为Bytecodes::_return_register_finalizer
  • Bytecodes::_return_register_finalizer指令的实现逻辑中会测试该类的JVM_ACC_HAS_FINALIZER访问标识
    • 如果JVM_ACC_HAS_FINALIZER置1,则转到InstanceKlass::register_finalizer方法中调用java.lang.ref.Finalizer.register(this),将创建对象注册为Finalizer,自身作为referent
    • 如果JVM_ACC_HAS_FINALIZER置0,则跳过register finalizer
  • 结论:如果RegisterFinalizersAtInit=true,那么在类链接阶段会j将Object构造函数中的return指令重写为_return_register_finalizer,等到实际创建该对象通过invokespecial调用其构造函数返回时,Object构造函数中重写的_return_register_finalizer指令逻辑中会判断该类的accessflags是否置一JVM_ACC_HAS_FINALIZER,如果已经设置将该对象注册为一个Finalizer,自身作为referent,否则跳过register Finalizer
    • 因为java.lang.Object是所有Java类的父类,而类的构造函数中第一句代码默认都是super调用父类构造函数,所以最终都会调用到Object的构造函数;
    • 如果一个类重写了java.lang.Object.finalize()方法,那么在链接该类的时候,就会将原来编译Object构造函数中的return指令重写为_return_register_finalizer;
    • 如果一个类没有重写java.lang.Object.finalize()方法,那么在链接该类的时候,就不会重写原来编译的Object构造函数中的return指令

为什么重写的是Object构造函数中的return指令?

因为Object是所有类的父类,实例化任何对象最终都会调用到Object的构造函数,这样重写了Object的构造函数,就不需要重写各个子类的构造函数(那些重写了object finalize方法的类),更高效。

代码等价于在Object的构造函数中调用Finalizer.register(this),其效果和Finalizer.register(referent)是一样的,见下例,父类构造函数中访问this,this指向的实际上是子类(是new指令初始化完成的this):

public class Father {public Father() {System.out.println("father构造函数中访问this: " + this);}
}
public class Son extends Father {public Son() {System.out.println("son构造函数中访问this: " + this);}
}
       Son son = new Son();System.out.println(son);

father构造函数中访问this: cn.wxy.Son@312b1dae
son构造函数中访问this: cn.wxy.Son@312b1dae
cn.wxy.Son@312b1dae


四、finalization机制

finalization机制其实就是调用对象重写java.lang.Object.finalize()方法来释放资源的机制,包含两个部分:

  • 第一部分:虚拟机将重写java.lang.Object.finalize()方法的对象封装成Finalizer,包含两个关键点:

    • 类加载阶段设置类访问标识JVM_ACC_HAS_FINALIZER
    • 虚拟机参数RegisterFinalizersAtInit
      • RegisterFinalizersAtInit=true:类连接阶段将构造函数return指令重写为_return_register_finalizer,等到运行时创建对象,invokespecial指令调用构造函数返回时会通过重写的_return_register_finalizer指令将新建对象封装为Finalizer
      • RegisterFinalizersAtInit=false:运行时创建对象,在通过new指令分配内存时会将新建对象封装为Finalizer
  • 第二部分:当第一部分虚拟机封装的Finalizer的referent失去强引用时,经Java引用机制,最终会被FinalizerThread线程调用其finalize方法,执行自定义释放资源的逻辑,这一部分包含两个连接的生产者消费者模型:
    • 第一个生产者消费者模型:GC -> pending-reference list -> reference-handler线程
    • 第二个生产者消费者模型:reference-handler线程 -> ReferenceQueue -> FinalizerThread线程

我们在第二章《Reference & ReferenceQueue & ReferenceHandler》谈Reference源码中referent属性的注释时,关于FinalReference的处理当时没有说完。

通过第二章referent属性的源码注释可知,在GC将FinalReference/Finalizer挂到pending-reference list上时:

  • 不会像其他Java引用类型一样clear referent,即FinalReference.referent = null;
  • 但是会设置FinalReference.next = this(next属性默认值为null);

finalization机制第一部分和第二部分通过GC来衔接,hotspot中GC对Java引用进行预处理的入口ReferenceProcessor::discover_reference函数,在该函数中就会对FinalReference的next属性做检测,用来判断该FinalReference是否已经被处理过了,源码如下:

bool ReferenceProcessor::discover_reference(oop obj, ReferenceType rt) {// Make sure we are discovering refs (rather than processing discovered refs).if (!_discovering_refs || !RegisterReferences) {return false;}if ((rt == REF_FINAL) && (java_lang_ref_Reference::next(obj) != NULL)) {// Don't rediscover non-active FinalReferences.return false;}// 省略很多源码
}

ReferenceProcessor::discover_reference函数中,如果待处理的对象是FinalReference/Finalizer,并且其next属性不等于null,那说明该FinalReference/Finalizer已经被处理过了,所以不需要再重复操作。

整体流程:

  • 场景:一个类重写了java.lang.Object.finalize()方法
  • 在类加载阶段:将该类的class文件加载到持久代/metaspace并生成对应的java.lang.Class时会解析class文件,如果发现方法名为finalize、返回值void、方法体非空,则会设置该类的JVM_ACC_HAS_FINALIZER访问标识
  • 在类链接阶段:在类链接阶段hotspot会重写一些虚拟机指令
    • 如果参数RegisterFinalizersAtInit=true,则会将class文件中原来编译的Object的构造函数中的return指令重写为_return_register_finalizer指令;
    • 如果参数RegisterFinalizersAtInit=false,则不重写指令;
  • 等到运行时创建对象
    • 先通过new指令为新建对象分配内存:如果RegisterFinalizersAtInit=false并且该类被标记为JVM_ACC_HAS_FINALIZER类,那么创建对象通过new指令分配内存时就会调用java.lang.ref.Finalizer.register(referent)将新建对象封装为一个Finalizer,自身作为referent
    • 接着通过invokespecial指令调用对象的构造方法做初始化:如果RegisterFinalizersAtInit=true并且该类被标记为JVM_ACC_HAS_FINALIZER类,那么创建对象通过invokespecial指令调用其构造函数返回时,类链接阶段重写的_return_register_finalizer指令会调用java.lang.ref.Finalizer.register(referent)将新建对象封装为一个Finalizer,自身作为referent
  • 在运行的过程中
    • 当Finalizer的referent失去强引用后,GC时发现referent的可达性发生变化,就会将Finalizer挂到pending-reference list;
    • 随后reference-handler线程从pending-reference list取走Finalizer对象,并将其放入关联的引用队列;
    • 接着FinalizerThread通过引用队列拿到Finalizer封装的referent并调用其finalize方法;
    • 最后,断开Finalizer和referent之间的引用关系,使referent变成真正的不可达状态

流程简述:

  • 如果重写了java.lang.Object.finalize()方法,那么该类的JVM_ACC_HAS_FINALIZER访问标识会被置位
  • 此时,根据虚拟机参数RegisterFinalizersAtInit:
    • -XX:+RegisterFinalizersAtInit,表示创建对象在调用其构造函数返回时将该对象封装为Finalizer,自身作为referent
    • -XX:-RegisterFinalizersAtInit,表示创建对象在分配内存空间时将该对象封装为Finalizer,自身作为referent
  • 当Finalizer的referent失去强引用后,GC时发现referent的可达性发生变化,就会将Finalizer挂到pending-reference list;
  • 随后reference-handler线程从pending-reference list取走Finalizer对象,并将其放入关联的引用队列;
  • 接着FinalizerThread通过引用队列拿到Finalizer封装的referent并调用其finalize方法;
    • 其中finalize方法是我们自定义的资源释放逻辑,需要重写java.lang.Object.finalize()方法
  • 最后,断开Finalizer和referent之间的引用关系,使referent变成真正的不可达状态

五、Runtime.runFinalization()

Finalizer源码中和Runtime相关的两个方法:runFinalization和forkSecondaryFinalizer。

转到Runtime中查看:

SharedSecrets,还记得第一章中谈java.lang.ref.Reference中的静态代码块吗,回看一下。

所以,Finalizer.runFinalization方法的流程是这样的:

  • 在java.lang.ref.Reference类加载的时候,SharedSecrets在相关的静态代码块中就准备好了;
  • 然后调用Runtime.runFinalization方法的时候,会转到Finalizer.runFinalization();
  • 在Finalizer.runFinalization()方法中会转调forkSecondaryFinalizer
  • forkSecondaryFinalizer的逻辑:
    • 新建一个线程Secondary finalizer
    • Secondary finalizer的线程逻辑和FinalizerThread差不多,都是从finalizer.queue中拿到Finalizer对象,并调用其runFinalizer方法,最终调用referent.finalze()执行资源清理逻辑;
    • 不同的是:
      • FinalizerThread线程是queue.remove拿Finalizer对象,如果队列为空,那么FinalizerThread线程会lock.wait阻塞在remove的死循环中,FinalizerThread是一个后台线程,最终会随着虚拟机进程的结束而结束;
      • Secondary finalizer是queue.poll拿Finalizer对象,如果队列为空,那么Secondary finalizer不会阻塞,而会结束循环,Secondary finalizer线程结束运行

如果某一时刻你想跑一跑Finalizer.queue中Finalizer封装的referent的finalize方法,可以Runtime.runFnalization(),跑完了Secondary finalizer线程会自动退出,而ReferenceQueue是线程安全的,虽然Secondary finalizer和FinalizerThread两个线程都在消费,但不会同步出错。


六、finalize方法

finalization机制太过复杂,还有一堆问题,终于在JDK9被标注弃用了。

弃用的原因:

  • The finalization mechanism is inherently problematic.
  • Finalization can lead to performance issues, deadlocks, and hangs.
    • finalization机制中执行referent.fianlize()方法依托于FinalizerThread,如果在finalize方法阻塞、挂起FinalizerThread,那就会导致很多问题
  • Errors in finalizers can lead to resource leaks;
  • there is no way to cancel finalization if it is no longer necessary;
  • and no ordering is specified among calls to finalize methods of different objects.
  • Furthermore, there are no guarantees regarding the timing of finalization.
  • The finalize method might be called on a finalizable object only after an indefinite delay, if at all.

并且给了明确的替代:

Classes whose instances hold non-heap resources should provide a method to enable explicit release of those resources, and they should also implement AutoCloseable if appropriate.

The Cleaner and PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.

  • 对于非堆资源,应该显式释放,你可以让拥有非堆资源的类实现java.lang.AutoCloseable;
  • 除此之外,当对象不可达时,PhantomReference和java.lang.ref.Cleaner类提供了更灵活、更有效的资源回收方式;

所以,上面洋洋洒洒的谈了那么多,实际上不建议你使用finalization机制来回收非堆资源。

再补充一些体面的废话,如果你真的使用了finalization机制,那么重写java.lang.Object.finalize()方法的原则:

A subclass should avoid overriding the finalize method unless the subclass embeds non-heap resources that must be cleaned up before the instance is collected. Finalizer invocations are not automatically chained, unlike constructors. If a subclass overrides finalize it must invoke the superclass finalizer explicitly. To guard against exceptions prematurely terminating the finalize chain, the subclass should use a try-finally block to ensure super.finalize() is always invoked. For example,

     @Overrideprotected void finalize() throws Throwable {try {... // cleanup subclass state} finally {super.finalize();}}

总结

  • 了解finalization机制
  • 上半部分有两个关键点:虚拟机参数RegisterFinalizersAtInit和类范文标志JVM_ACC_HAS_FINALIZER
    • 核心:什么样的对象在什么时候会被虚拟机封装成Finalizer
  • 下半部分涉及两个相连的生产者消费者模型
    • 第一个生产者消费者模型:GC -> pending-reference list -> reference-handler线程
    • 第二个生产者消费者模型:reference-handler线程 -> ReferenceQueue -> FinalizerThread线程
  • Finalizer和FinalizerThread的源码逻辑
  • Runtime.runFinalization()
  • 对于非堆资源的回收,知晓finalization机制已被放弃,以及两种替代方式

【java.lang.ref】FinalReference Finalizer FinalizerThread相关推荐

  1. 【java.lang.ref】SoftReference WeakReference

    目录 零.前情概要 ref包内容 系列目录 上一章回顾 一.WeakReference 适用场景 测试案例 应用举例 二.SoftReference的适用场景 三.量化软引用"内存紧张&qu ...

  2. 【java.lang.ref】当WeakReference的referent重写了finalize方法时会发生什么

    问题 question:当WeakReference的referent重写了finalize方法时会发生什么? 测试代码 JVM中是存在这样的情况的:一个Java对象,重写了finalize方法,在使 ...

  3. 【java.lang.UnsupportedClassVersionError】版本不一致出错

    这种错误的全部报错信息: 1 java.lang.UnsupportedClassVersionError: org/apache/lucene/store/Directory : Unsupport ...

  4. 【java.lang.UnsupportedClassVersionError】问题的解决方法

    程序部署到多个服务器的时候其中报出错误: nohup: ignoring input and appending output to `nohup.out' 查看nohup.out日志 Excepti ...

  5. java lang r,内存泄漏?为什么java.lang.ref.Finalizer吃了这么多内存

    I ran a heap dump on my program. When I opened it in the memory analyzer tool, I found that the java ...

  6. 深入探讨 java.lang.ref 包--转

    概述 Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Java 垃圾回收器密切相关的引用类.这些引用类对象可以指向其它对象,但它们不同于一般的引用,因为它们的存在并不防碍 ...

  7. 深入探讨 java.lang.ref 包

    http://www.ibm.com/developerworks/cn/java/j-lo-langref/ 概述 Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Ja ...

  8. java ref object_深入探讨 java.lang.ref 包

    概述 Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Java 垃圾回收器密切相关的引用类.这些引用类对象可以指向其它对象,但它们不同于一般的引用,因为它们的存在并不防碍 ...

  9. 【重难点】【Java基础 01】一致性哈希算法、sleep() 和wait() 的区别、强软弱虚引用

    [重难点][Java基础 01]一致性哈希算法.sleep() 和wait() 的区别.强软弱虚引用 文章目录 [重难点][Java基础 01]一致性哈希算法.sleep() 和wait() 的区别. ...

最新文章

  1. “深度学习一点也不难!”
  2. [李景山php]每天TP5-20170131|thinkphp5-Request.php-3
  3. POJ 3237.Tree -树链剖分(边权)(边值更新、路径边权最值、区间标记)贴个板子备忘...
  4. Ubuntu 16.04 LTS今日发布
  5. 【原创】MySQL5.7 虚拟列实现表达式索引
  6. Why Most Popular JavaScript libraries doesn’t support XML with namespaces?
  7. 未能加载文件或程序集“Antlr3.Runtime”或它的某一个依赖项。参数错误。 (异常来自 HRESULT:0x80070057 (E_INVALIDARG))解决方法。...
  8. 进制转换练习-其它进制转换为十进制
  9. python 对redis key的基本操作
  10. html td中加label,html – td对齐内的2个标签
  11. php 不支持curl 的解决方案
  12. EF架构~将数据库注释添加导入到模型实体类中
  13. Qt线程和signal-slot
  14. Android调用手机摄像头
  15. 华为交换机导入配置_华为交换机配置教程 华为核心交换机配置
  16. Qt 小例子学习9 - 代码编辑器
  17. Eureka Server报错:Retry limit reached; giving up on complet the request
  18. vue分页组件,可直接使用
  19. Telegram被封禁的原因
  20. el-table拆分单元格

热门文章

  1. 文本框直接粘贴添加图片构想
  2. 微信小程序的的图片显示不出来
  3. [Unity3d]刀斧武器砍击的攻击碰撞判定
  4. Joda-Time 简介
  5. python学习笔记(07)---(内置容器-字典)
  6. 《非暴力沟通》读后感
  7. IOS 控制键盘升降
  8. 美团外卖【成都】技术团队,招人啦!
  9. mysql 取top 10_我的mysql如何分组取top10?
  10. PHP 小程序中微信支付