Java ~ Reference ~ Finalizer【源码】

前言


 文章

一 Finalizer(终结者)类源码及机制详解


 类

    Finalizer(终结者)类是FinalReference(终引用)类的子类,其访问权限为DEFAULT,即只有同一个包下的类才能够进行调用,且其被final关键字修饰,因此可知其无法再被其它子类继承。

/**
 * @Description: 终结者类,直接继承自最终引用类
 */
final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
                                                          same package as the Reference
                                                          class */
    ...
}

 字段

    queue(引用队列) —— 该字段是一个静态字段,其默认了一个ReferenceQueue(引用队列)类的实例,被作为Finalizer(终结者)类对象注册引用队列使用,因此可知所有的Finalizer(终结者)类对象注册的都是同一个引用队列。事实上,这个默认的ReferenceQueue(引用队列)类对象就是finalization(终结)机制中令人熟知的f-queue。

/**
 * @Description: 引用队列,这个队列就是传说中的f-queue。
 */
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

    unfinalized(未终结的) —— 该字段是一个静态字段,被用于保存未终结链表的头Finalizer(终结者)类对象(获取了头Finalizer(终结者)类对象,就相当于获取了整个未终结链表),因此可知在整个JVM中只存在一个未终结链表。未终结链表的作用是为了保证Finalizer(终结者)类对象不会被GC回收,如果没有未终结链表会导致Finalizer(终结者)类对象加入引用队列之前不可达(即不存在GC Roots关联的链路),这会使其被GC回收。

/**
 * @Description: 未终结链表的头终结者(静态字段,因此只有一个未终结链表。)
 */
private static Finalizer unfinalized = null;

    Finalizer(终结者)类对象会在何时加入未终结链表中呢?粗略的说是在F类对象创建的时候。F类对象创建时,JVM将之作为所指对象调用Finalizer(终结者)类的register()方法创建一个Finalizer(终结者)类对象并加入到未终结链表中。实际上关于Finalizer(终结者)类对象的加入时间还能有更加精确的描述及调整。众所周知Java对象的创建并不是一个原子操作,其大致可以分为两个部分:实例化(分配内存)与初始化(执行构造方法)。通过对JVM参数-XX:+RegisterFinalizersAtInit的设置(默认为+),可以令Finalizer(终结者)类对象在F类实例化后、初始化前的时间段创建加入(-XX:-RegisterFinalizersAtInit),也可以令其在F类初始化后创建加入(-XX:+RegisterFinalizersAtInit)。

void Heap::AddFinalizerReference(Thread* self, ObjPtr<mirror::Object>* object) {
    ScopedObjectAccess soa(self);
    ScopedLocalRef<jobject> arg(self->GetJniEnv(), soa.AddLocalReference<jobject>(*object));
    jvalue args[1];
    args[0].l = arg.get();
    // 调用 Java 层静态方法 FinalizerReference#add
    InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_FinalizerReference_add, args);
    *object = soa.Decode<mirror::Object>(arg.get());
}

    JVM是如何做到在指定的时间里调用Finalizer(终结者)类的register()方法的呢?这个就要根据不同的情况判断。针对在实例化后、初始化前调用的F类对象,由于内存分配是统一由内存管理系统负责的,因此可以由其负责处理;而对于在初始化后的F类对象则相对负责,难道要在所有的构造方法尾部都进行处理吗?当然是不可能。实际上JVM使用了一个很灵活的方式,在Java中,子类的构造方法会调用父类的构造方法,因此最终都会执行Object(对象)类的构造方法。JVM将Object(对象)类的构造方法里的return指令替换为_return_register_finalizer指令,从而在侵入性很小的情况下完成了方法调用。

    lock(锁) —— 用于在操作未终结链表时作为synchronized关键字的锁对象使用。该字段是一个静态字段,因此可知是一个类锁。

/**
 * @Description: 类锁对象
 */
private static final Object lock = new Object();

    next(前驱终结者)/prev(后继终结者) —— 用于持有当前Finalizer(终结者)类对象在未终结链表中后继/前驱Finalizer(终结者)类对象的引用,因此可知未终结链表是一个双向链表。当Finalizer(终结者)类对象从未终结链表中移除时,会将两者都指向自身,因此可以由此判断一个Finalizer(终结者)类对象是否移除。

/**
 * @Description: 后继终结者/前驱终结者
 */
private Finalizer next = null, prev = null;

 静态块

    静态块中创建了一个FinalizerThread(终结者线程)类对象,也就是所谓的终结者线程,因此可知终结者线程会在Finalizer(终结者)类的类加载过程的初始化步骤完成创建。终结者线程专用于对f-queue中的Finalizer(终结者)类对象进行处理,即执行其所指对象/F类对象的finalize方法。终结者线程是一个守护线程,其优先级为8。很多资料都会说其优先级很低,实际上这是相对而言的,毕竟对于大部分优先级为5的用户线程而言8确实不算低,但相比于常规优先级为10的守护线程,其也确实低了些。

static {
    // 获取当前线程所在的线程组。
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent())
        ;
    // 实例化的终结者线程,设置其优先级为8(相对其他守护线程会低一些)且为守护线程,并执行。
    Thread finalizer = new FinalizerThread(tg);
    finalizer.setPriority(Thread.MAX_PRIORITY - 2);
    finalizer.setDaemon(true);
    finalizer.start();
}

 构造方法

    Finalizer(终结者)类只有一个构造方法,通过调用父类FinalReference(终引用)类的构造方法实现。其将queue(引用队列)字段的默认ReferenceQueue(引用队列)类对象作为Finalizer(终结者)类对象的注册引用队列,因此可知所有的Finalizer(终结者)类对象注册的都是同一个引用队列。与此同时该构造方法还会将Finalizer(终结者)类对象加入到未终结链表中。该构造方法是私有的,因此可知Finalizer(终结者)类对象无法直接在类的外部使用new创建。

private Finalizer(Object finalizee) {
    // 采用一个静态的引用队列作为终结者的引用队列,因此可知所有的终结者注册的都是同一个引用队列。
    super(finalizee, queue);
    // 将终结者对象加入未终结链表中。
    add();
}

 方法

    static void register(Object finalizee) —— 注册 —— 即实例化一个Finalizer(终结者)类对象并将之加入注册队列中,是直接调用构造方法实现的。register()是供JVM使用的,因此除非使用反射,否则开发者无法调用该方法。JVM会在F类创建时调用这个方法。

/**
 * @Description: 注册终结者(即创建一个终结者并加入终结者链表,该方法由JVM进行调用)
 */
/* Invoked by VM */
static void register(Object finalizee) {
    new Finalizer(finalizee);
}

    private boolean hasBeenFinalized() —— 是否终结(false:否,true:是) —— 本质是判断当前Finalizer(终结者)类对象是否已经从未终结链表中移除。上文已经提及当Finalizer(终结者)类对象从未终结链表中移除时,会将next(后继终结者)/prev(前驱终结者)字段指向自身,因此可以由此判断一个Finalizer(终结者)类对象是否移除。

/**
 * @Description: 是否终结(即判断当前终结者对象是否已经从终结者链表中移除)
 */
private boolean hasBeenFinalized() {
    // 终结者对象从终结者链表中移除时,会将前驱终结者/后继终结者设置为自身,因此只需判断是否为自身即可。
    return (next == this);
}

    private void add() —— 新增 —— 将当前Finalizer(终结者)类对象添加进未终结链表中。该方法采用的是头插法,即新加入的Finalizer(终结者)类对象会成为头Finalizer(终结者)类对象,而原Finalizer(终结者)类对象则会成为其后继Finalizer(终结者)类对象。

/**
 * @Description: 将自身添加进终结者链表中
 */
private void add() {
    // 在类锁的保护下执行。
    synchronized (lock) {
        // 采用头插法,将新加入的终结者设置为新头终结者,将旧头终结者设置为后继终结者。
        if (unfinalized != null) {
            this.next = unfinalized;
            unfinalized.prev = this;
        }
        unfinalized = this;
    }
}

    private void remove() —— 移除 —— 将当前Finalizer(终结者)类对象从未终结链表中移除。将Finalizer(终结者)类对象从未终结链表中移除并不需要遍历,因为其保存了前驱/后继Finalizer(终结者)类对象的引用,因此只需将前驱/后继Finalizer(终结者)类对象重新建立关联后将自身弹出即可。弹出的同时,需要将next(后继终结者)/prev(前驱终结者)字段指向自身,作为Finalizer(终结者)类对象已经移除的标志。

/**
 * @Description: 将自身从未终结链表中移除
 */
private void remove() {
    // 在类锁的保护下进行操作。
    synchronized (lock) {
        //     如果当前终结者是头终结者,判断是否存在后继终结者,是则将之设置为新头终结者,否则则将前驱终结者设置为新头终结者...
        // 关于将前驱终结者设置为头终结者这一步看不太懂。
        if (unfinalized == this) {
            if (this.next != null) {
                unfinalized = this.next;
            } else {
                unfinalized = this.prev;
            }
        }
        // 将当前终结者从未终结链表中弹出,并重新建立起前驱终结者与后继终结者的关联关系。
        if (this.next != null) {
            this.next.prev = this.prev;
        }
        if (this.prev != null) {
            this.prev.next = this.next;
        }
        // 将当前终结者的后继终结者/前驱终结者设置为自身,用于移除的标志。
        this.next = this;   /* Indicates that this has been finalized */
        this.prev = this;
    }
}

    private void runFinalizer(JavaLangAccess jla) —— 执行终结者 —— 该方法用于将Finalizer(终结者)类对象从未终结链表中移除,同时还负责执行其所指对象的finalize()方法,并在finalize()方法执行结束后断开与Finalizer(终结者)类对象的连接以令其可被GC回收。关于上述流程存在以下几个主要注意的点:

    finalization(终结)机制只保证finalize()方法一定会调用,但不保证会等待其执行结束。这是目前的主流说法,包括《深入理解Java虚拟机》一书中也是这么描述的。虽然从代码上看,似乎会执行完finalize()方法才会断开所指对象与Finalizer(终结者)类对象之间的关联时期被GC回收。但如果主流说法是正确的话,那关于finalize()方法的具体执行一定还有其它的影响条件。

    finalize()方法只能被执行一次。一个F类对象之所以只能执行一次finalize()方法,是因为其在执行完finalize()方法后会断开其与Finalizer(终结者)类对象的关联。因此,即使在finalize()方法中将F类对象成功复活(即与GC Roots建立上关联),其也不再有Finalizer(终结者)类对象持有它了。一个没有Finalizer(终结者)类对象的F类对象自然是无法执行finalize()方法的。

    F类对象至少要两次GC才能被真正回收。第一次GC将F类对象判定为可回收并执行finalization(终结)机制机制;第二次GC则会将执行完finalize()方法并断开与Finalizer(终结者)类对象关联的F类对象正式回收。而在这两次GC之间也可能存在有多次的GC,因此是至少两次。

/**
 * @Description: 运行终结者
 */
private void runFinalizer(JavaLangAccess jla) {
    // 在同步的环境下执行。
    synchronized (this) {
        //     判断当前终结者对象是否已经终结,即判断其是否已从未终结链表中移除。是则直接返回,否则移除。
        if (hasBeenFinalized()) return;
        remove();
    }
    // 在catch块中执行终结者对象的所指对象的finalize()方法,目的是确保执行异常时不会中断终结者线程。
    try {
        Object finalizee = this.get();
        if (finalizee != null && !(finalizee instanceof Enum)) {
            //     调用所指对象的finalize()方法。这种调用方式具体还有待研究,因为有一种说法是JVM虽然可以保证对象的finalize()方
            // 法一定会被调用(如果这个说法是对的话,那finalizee != null判断应该是非必要的),但无法保证其一定能执行结束,因为
            // 如果重写后的finalize()方法执行时间很长,可能会导致整体流程收到影响,最典型的就是大量的终结者对象来不及处理导致
            // 堆积,甚至触发OOM...但从这里来看似乎是能保证的,除非invokeFinalize()方法有什么骚操作。
            jla.invokeFinalize(finalizee);

            /* Clear stack slot containing this variable, to decrease
               the chances of false retention with a conservative GC */
            // 清除引用,以确保不会被GC错误的遗留。
            finalizee = null;
        }
    } catch (Throwable x) {
        // 捕捉所有的异常,以确保不会中断终结者线程。
    }
    //     清除所指对象与终引用对象(终结者对象)之间的关系,以确保所指对象顺利GC,而在此之前,所指对象不会被回收。在存
    // 在终引用对象的情况下,所指对象至少需要两次GC才能被回收。一次判定其可被回收;一次是调用了finalize()方法后被后面的
    // GC正式回收,而在这两次中间也可能发生了多次GC,因此是至少两次。
    super.clear();
}

    private static void forkSecondaryFinalizer(final Runnable proc) —— 分叉二级终结者 —— 该方法用于创建一个名义上二级终结者线程来执行自定义操作,并等待其完成操作。之所以说是名义上的二级终结者线程,是因为其使用的是常规的Thread(线程)类,而不是Finalizer(终结者)类中定义的FinalizerThread(终结者线程类)类。该方法在类中有两处调用:runFinalization()及runAllFinalizers()。前者的执行操作与正常流程相同,相当于新开了一个线程来进行辅助加速;而后者的执行操作则是上文中提及的直接跳过正常流程中引用队列的步骤从未终结链表中获取Finalizer(终结者)类对象并向下执行。

/* Create a privileged secondary finalizer thread in the system thread
   group for the given Runnable, and wait for it to complete.

   This method is used by both runFinalization and runFinalizersOnExit.
   The former method invokes all pending finalizers, while the latter
   invokes all uninvoked finalizers if on-exit finalization has been
   enabled.

   These two methods could have been implemented by offloading their work
   to the regular finalizer thread and waiting for that thread to finish.
   The advantage of creating a fresh thread, however, is that it insulates
   invokers of these methods from a stalled or deadlocked finalizer thread.
 */
private static void forkSecondaryFinalizer(final Runnable proc) {
    // 创建一个二级终结者线程执行自定义操作,并等待其完成。
    AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                @Override
                public Void run() {
                    ThreadGroup tg = Thread.currentThread().getThreadGroup();
                    for (ThreadGroup tgn = tg;
                         tgn != null;
                         tg = tgn, tgn = tg.getParent())
                        ;
                    // 可以看到这是一个常规的线程,因此所谓的二级终结者线程只是名义上的,并不是真正的终结者线程。
                    Thread sft = new Thread(tg, proc, "Secondary finalizer");
                    sft.start();
                    try {
                        // 阻塞主线程。
                        sft.join();
                    } catch (InterruptedException x) {
                        Thread.currentThread().interrupt();
                    }
                    return null;
                }
            });
}

    static void runFinalization() —— 执行终结 —— 该方法的代码逻辑与正常流程相同,且会调用forkSecondaryFinalizer()方法创建一个二级终结者线程执行。该方法在Runtime(运行)类中存在调用,而该类与程序运行息息相关,因此猜测该方法应该是程序在某些情况下(例如空间内存不足等)用于加速finalization(终结)机制所用,毕竟…两个线程处理肯定会比一个线程快一些。

/**
 * @Description: 运行终结(该方法Runtime类中有调用)
 */
/* Called by Runtime.runFinalization() */
static void runFinalization() {
    if (!VM.isBooted()) {
        return;
    }

    forkSecondaryFinalizer(new Runnable() {
        private volatile boolean running;

        @Override
        public void run() {
            // in case of recursive call to run()
            if (running)
                return;
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (; ; ) {
                //     从引用队列(f-queue)中取出终结者对象,如果引用队列中不存在终结者对象,则直接结束循环并退出线程。因此
                // 可知该方法可有助于加速引用队列及未终结链表中终结者的处理流程。
                Finalizer f = (Finalizer) queue.poll();
                // 执行终结者对象的所指对象的finalize()方法。
                if (f == null) break;
                f.runFinalizer(jla);
            }
        }
    });
}

    static void runAllFinalizers() —— 运行全终结者 —— 该方法会跳过正常流程中引用队列的步骤从未终结链表中获取Finalizer(终结者)类对象,且会调用forkSecondaryFinalizer()方法创建一个二级终结者线程执行。该方法在Shutdown(关闭)类中存在调用,而从该类的类名猜测,该方法应该是程序关闭前用于紧急处理剩余Finalizer(终结者)类对象所用,毕竟按照正常流程还需要等待GC判定回收F类回收。而如果在程序关闭前没有处理掉所有的Finalizer(终结者)类对象的话,就有可能导致资源泄露的危险(finalization(终结)机制一般都会用来保证资源回收/释放上)。

    可以看到finalization(终结)机制确实提供了可以紧急处理Finalizer(终结者)类对象的方式,但目前主流的说法是:当程序被关闭时,finalization(终结)机制可能会因为未执行完所有对象的finalize()方法而导致资源泄露。如果这个说法是正确的,那该方法的作用就是在程序仅存的存活时间中尽可能多的加速处理,并不能保证所有的Finalizer(终结者)类对象都被处理完毕,当程序退出时执行该方法的线程会被强制杀死。

/**
 * @Description: 运行全终结者(该方法在java.lang.Shutdown类中有调用)
 */
/* Invoked by java.lang.Shutdown */
static void runAllFinalizers() {
    if (!VM.isBooted()) {
        return;
    }

    forkSecondaryFinalizer(new Runnable() {
        private volatile boolean running;

        @Override
        public void run() {
            // in case of recursive call to run()
            if (running)
                return;
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (; ; ) {
                //     直接从未终结链表中获取终结者对象,并将之从未终结链表中移除并执行其所指对象的finalize()方法。当未终结链表
                // 为空时退出循环。该操作不会影响主流程,当主流程从引用队列中获取到终结者对象后,会先判断该终结者对象是否已
                // 经从未终结链表中移除,因此不会导致所指对象的finalize()方法重复调用。
                Finalizer f;
                synchronized (lock) {
                    f = unfinalized;
                    if (f == null) break;
                    unfinalized = f.next;
                }
                f.runFinalizer(jla);
            }
        }
    });
}

二 FinalizerThread(终结者线程类)类源码及机制详解


 类

    FinalizerThread(终结者线程类)类是Finalizer(终结者)类的一个私有静态内部类,其继承自Thread(线程)类,因此可知其对象为一个线程。

/**
 * @Description: 终结者线程类
 */
private static class FinalizerThread extends Thread {
    ...
}

 字段

    running(是否运行) —— 该字段用于判断finalization(终结)机制是否已经运行,以避免在递归的情况下重复执行…但从源码中似乎没有发现有递归调用的地方。

/**
 * @Description: 是否运行(false:否,true:是)
 */
private volatile boolean running;

 方法

    public void run() —— 运行 —— 该方法继承自Thread(线程)类,用于定义线程执行的任务。该方法中定义了finalization(终结)机制的正常流程。

@Override
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
    // 终结者线程先于System.initializeSystemClass被调用。等待直到JavaLangAccess可以访问...看不太懂什么意思。
    while (!VM.isBooted()) {
        // delay until VM completes initialization
        try {
            VM.awaitBooted();
        } catch (InterruptedException x) {
            // ignore and continue
        }
    }
    final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
    // 将运行状态设置为true,表示已经开始终结工作。
    running = true;
    // 死循环。
    for (; ; ) {
        try {
            // 从引用队列中获取终结者对象(remove方法是一个阻塞方法,会一直阻塞到能获取到对象为止)。
            Finalizer f = (Finalizer) queue.remove();
            // 执行终结者对象的runFinalizer()方法。
            f.runFinalizer(jla);
        } catch (InterruptedException x) {
            // ignore and continue
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

说淑人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值