目录
- 零、前情概要
- 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);
}
@Override
public 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 unfinalized
synchronized (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 available
while (VM.initLevel() == 0) {
// delay until VM completes initialization
try {
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 finalized
return;
// unlink from unfinalized
if (unfinalized == this)
unfinalized = this.next;
else
this.prev.next = this.next;
if (this.next != null)
this.next.prev = this.prev;
this.prev = null;
this.next = this; // mark as finalized
}
try {
// 拿到referent
Object 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之间的引用