【java.lang.ref】FinalReference & Finalizer & FinalizerThread

本文详细探讨了Java中的Finalization机制,包括FinalReference、Finalizer和FinalizerThread的工作原理。Finalizer机制是通过FinalizerThread线程调用对象的finalize方法来释放资源。文章介绍了Finalizer的注册过程,涉及到虚拟机参数RegisterFinalizersAtInit以及new和invokespecial指令的影响。此外,还分析了finalization机制的两个生产者消费者模型,以及如何避免使用finalize方法来回收资源的建议。
摘要由CSDN通过智能技术生成

目录

  • 零、前情概要
    • 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.系列目录

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之间的引用
          
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值