【JAVA Reference】Finalizer 剖析 (六)

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码

文章目录

      • 一、前言
      • 二、架构
        • 2.1 代码架构
        • 2.2 UML流程图
      • 三、思考 Finalizer Vs Cleaner 放一起
        • 3.1 代码 demo
        • 3.2 为什么 finalize 总是比 Cleaner 先执行 ?
      • 四、Finalizer 源码剖析
        • 4.1 父类 FinalReference
        • 4.2 Finalizer 类
        • 4.3 Finalizer 类变量
          • 4.3.1 私有static变量 queue
          • 4.3.2 私有static变量 unfinalized
          • 4.3.3 next + prev 指针
          • 4.3.3 私有 static final 变量 lock
        • 4.4 对象初始化
          • 4.4.1 构造方法
          • 4.4.2 真正初始化对象的 register 方法
        • 4.5 add 方法
        • 4.6 remove 方法
        • 4.7 hasBeenFinalized 方法
        • 4.8 static块
        • 4.9 内部类 FinalizerThread
          • 4.9.1 VM.isBooted() 方法
          • 4.9.2 SharedSecrets.getJavaLangAccess();
          • 4.9.3 死循环执行 runFinalizer
            • 4.9.3.1 注意一下 try-catch 吃掉异常
        • 4.10 runFinalizer 方法
        • 4.11 clear() 方法
        • 4.12 总结一下
      • 五、JVM负责调用的三方法
        • 5.1 Register方法
        • 5.2 runFinalization 方法
          • 5.2.1 Runtime#runFinalization 方法
          • 5.2.2 Finalizer.runFinalization 方法
          • 5.2.3 forkSecondaryFinalizer 方法
        • 5.3 runAllFinalizers 方法
          • 5.3.1 Shutdown#runAllFinalizers 方法
          • 5.3.2 Finalizer.runAllFinalizers 方法
        • 5.4 总结一下
          • 5.4.1 runAllFinalizers 与 runFinalization 的不同
      • 六、番外篇


一、前言

建议提前阅读:

  1. 【JAVA Reference】ReferenceQueue 与 Reference 源码剖析(二)
  2. 【JAVA Reference】Cleaner 源码剖析(三)
  3. 【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)

二、架构

2.1 代码架构

【JAVA Reference】Finalizer 剖析 (六)_第1张图片
【JAVA Reference】Finalizer 剖析 (六)_第2张图片

  • Finalizer 继承 FinalReference 再继承 Reference。

2.2 UML流程图

【JAVA Reference】Finalizer 剖析 (六)_第3张图片

=== 点击查看top目录 ===

三、思考 Finalizer Vs Cleaner 放一起

  • 我们知道重写 finalize 方法与定义一个 cleaner 都可以实现在gc回收前进行一系列操作。建议先看 【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)

3.1 代码 demo

public class _05_03_TestCleanerWithFinalize {
    public static void main(String[] args) throws Exception {
        int index = 0;
        while (true) {
            Thread.sleep(1000);
            // 提醒 GC 去进行垃圾收集了
            System.gc();

            // 该对象不断重新指向其他地方,那么原先指针指向的对象的就属于需要回收的数据
            DemoObject obj = new DemoObject("demo" + index++);
            Cleaner.create(obj, new CleanerTask("thread_" + index++));
        }
    }

    @Data
    @AllArgsConstructor
    @ToString
    static class DemoObject {
        private String name;

        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize running DoSomething ..." + name);
        }
    }

    static class CleanerTask implements Runnable {
        private String name;

        public CleanerTask(String name) {
            this.name = name;
        }

        // do something before gc
        @Override
        public void run() {
            System.out.println("CleanerTask running DoSomething ..." + name );
        }
    }
}

输出:

finalize running DoSomething ...demo0
CleanerTask running DoSomething ...thread_1
finalize running DoSomething ...demo2
CleanerTask running DoSomething ...thread_3
finalize running DoSomething ...demo4
CleanerTask running DoSomething ...thread_5
finalize running DoSomething ...demo6
CleanerTask running DoSomething ...thread_7
finalize running DoSomething ...demo8
...

可以看到,每次 finalize 总是比 Cleaner 先执行,不管你run几次,结果都一样,那么思考一下为什么?

=== 点击查看top目录 ===

3.2 为什么 finalize 总是比 Cleaner 先执行 ?

  • 结论先抛出来: Cleaner 和 finalize 内部都有指针Pointer 指向了即将要回收的 Object 对象,但是 Cleaner 底层是虚引用(PhamtonReference),而 finalize的底层是 Finalizer ,属于强引用。所以,必须强引用的释放完对象,才轮到 Cleaner。

=== 点击查看top目录 ===

四、Finalizer 源码剖析

4.1 父类 FinalReference

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
  • 注意:这个类的修饰符是 default ,也就是只能在包 java.lang.ref 内被使用 。
  • 由继承关系可以得知,FinalReference 只有一个子类 Finalizer
    【JAVA Reference】Finalizer 剖析 (六)_第4张图片

=== 点击查看top目录 ===

4.2 Finalizer 类

final class Finalizer extends FinalReference<Object> 
  • 注意,Finalizer 是个 final 类,也就是断子绝孙类,不会有继承,可以防止篡改与注入。
  • Finalizer 类的修饰符是 default ,也就是只能在包 java.lang.ref 内被使用 。那么总览了一下,他是 JVM 调用的。

4.3 Finalizer 类变量

4.3.1 私有static变量 queue

Finalizer引用的对象被gc之前,jvm会把相应的Finalizer对象放入队列 queue

    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

=== 关于Reference的四个状态,可以看图 ===
=== 点击查看top目录 ===

4.3.2 私有static变量 unfinalized

静态的Finalizer对象链,每=== 实例化 ===一个对象,这个队列就会插入=== add ===一个。

    // 静态的Finalizer对象链
    private static Finalizer unfinalized = null;

=== 点击查看top目录 ===

4.3.3 next + prev 指针
    // 双端指针
    private Finalizer
        next = null,
        prev = null;

=== 点击查看top目录 ===

4.3.3 私有 static final 变量 lock
    private static final Object lock = new Object();
  • 每次对 unfinalized 队列进行 add 与 remove 的时候,都会进行加锁。

=== 点击查看top目录 ===

4.4 对象初始化

4.4.1 构造方法
  • 私有构造方法,说明无法通过 new 实例化
private Finalizer(Object finalizee) {
		// 4.4.1 初始化
        super(finalizee, queue);
        // 4.5 往前面插入
        add();
    }
  • 看下 super 构造函数
    传入两个参数: referent 与 ReferenceQueue
    有了 ReferenceQueue 说明可以从pending 进入到 Enqueue 队列。
    === 假如未注册 ReferenceQueue,那么不会进入 Enqueue 队列 ===
class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

=== 点击查看top目录 ===

4.4.2 真正初始化对象的 register 方法
  • java.lang.ref.Finalizer#register
	/* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }
  • 注册 Finalizer 对象,只要是类 override Finalize 方法的,初始化类的对象后,都会被VM注册到这里来。
  • 参数 Object finalizee ,表示指针指向的对象。

=== 点击查看top目录 ===

4.5 add 方法

往队列 unfinalized 的头部插入

// 往头部插
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

=== 点击查看top目录 ===

4.6 remove 方法

往队列 unfinalized 的头部移除

    // 从头部移除
    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;
            }
            // next 和 prev 都指向自己,表明已经被 remove 掉了,准备调用类重新复写的 finalize 方法
            this.next = this;   /* Indicates that this has been finalized */
            this.prev = this;
        }
    }

=== 点击查看top目录 ===

4.7 hasBeenFinalized 方法

  • 是否已经执行了 finalize 方法,如果 this.next = this; this.prev = this; 那么就是说明已经执行了。 因为 runFinalizer 的内部调用了 remove 方法
    private boolean hasBeenFinalized() {
        return (next == this);
    }

=== 点击查看top目录 ===

4.8 static块

    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); // 特地让出了优先级,这个跟cleaner还不一样
        finalizer.setDaemon(true); // 幽灵线程,这个跟 cleaner 一样
        finalizer.start(); // 把线程给跑起来
    }
  1. 启动一个内部类 FinalizerThread 线程。
  2. 设置优先级是 Thread.MAX_PRIORITY - 2,Cleaner 的优先级是 MAX_PRIORITY。虽然Cleaner的优先级比 FinalizerThread 高,但是由于 FinalizerThread 有强引用,所以 Cleaner 还是比较晚地执行。
  3. setDaemon 幽灵线程,这个跟 cleaner 一样
  • 这个静态代码块跟 === Reference的static代码块 ReferenceHandler=== 很相近 。

  • 接下来看下内部类 FinalizerThread 是干什么的!!!
    === 点击查看top目录 ===

4.9 内部类 FinalizerThread

  • java.lang.ref.Finalizer.FinalizerThread 私有静态内部类
private static class FinalizerThread extends Thread {
        // 这个参数用来判断该线程是否已经启动
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        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
            // Finalizer线程在 System.initializeSystemClass 被调用前启动
            // 需要等到JVM已经初始化完成才能执行
            // 4.9.1
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                // 延迟等待 JVM 完全初始化完毕
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            // 4.9.2
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // getter 直接看 Reference 类的 static 代码块 
            running = true;
            // 4.9.3
            for (;;) {
                try {
                    // 将 Finalizer 从 queue 队列中拿出来然后调用完方法后释放
                    // Active -> pending -> enqueue -> 出队列,进行处理
                    Finalizer f = (Finalizer)queue.remove();
                    // 调用其runFinalizer方法,实质是要去调用该对象覆写的 finalize 方法
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                    // 这个地方要注意,你重写的 finalize 方法,里面抛出的异常,是直接忽略的
                }
            }
        }
    }

=== 点击查看top目录 ===

4.9.1 VM.isBooted() 方法

判断 JVM 是否已经启动,如果还没启动,那么 VM.awaitBooted() 等待 JVM 启动再说。

=== 点击查看top目录 ===

4.9.2 SharedSecrets.getJavaLangAccess();
  • setter 在 java.lang.System#setJavaLangAccess 方法内部

=== 点击查看top目录 ===

4.9.3 死循环执行 runFinalizer
for (;;) {
     try {
         // 将 Finalizer 从 queue 队列中拿出来然后调用完方法后释放
         // Active -> pending -> enqueue -> 出队列,进行处理
         Finalizer f = (Finalizer)queue.remove();
         // 调用其runFinalizer方法,实质是要去调用该对象覆写的 finalize 方法
         f.runFinalizer(jla);
     } catch (InterruptedException x) {
         // ignore and continue
         // 这个地方要注意,你重写的 finalize 方法,里面抛出的异常,是直接忽略的
     }
 }
  • queue.remove(); 把 queue 中的 Referece 全部拿出来,然后调用他们override的finalize方法。

=== 点击查看top目录 ===

4.9.3.1 注意一下 try-catch 吃掉异常
  • 这个地方直接就把异常吃掉了,那么类定义的 finalize方法不管出现什么情况,都不会有异常打印出来,也不会对异常做任何处理。(即使你调用了 10 / 0),=== 具体看代码Demo#2.1.2 ===

=== 点击查看top目录 ===

4.10 runFinalizer 方法

  • java.lang.ref.Finalizer#runFinalizer
    // 执行 finalize 方法,然后进行GC回收
    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            // 只能执行1次,如果已经执行了。那么直接 return 回收
            if (hasBeenFinalized()) return;
            //执行 remove方法 ,从 unfinalized 链中拿掉该节点,然后返回出来,有点类似于 pop 操作
            remove(); 
        }
        try {
            // 拿到 指针指向的对象
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                // 对象调用 finalize 方法
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null; //设置为空
            }
        } catch (Throwable x) { }
        // 4.9.5 解除强引用
        super.clear(); 
    }
  1. 先判断是否已经执行过 finalize 方法了,是的话,就返回。所以记住,finalize方法只会执行一次。
  2. 调用 remove() 方法,把 next 和 prev都指向自己,所以刚刚方法hasBeenFinalized判断是否已经处理过的依据,就是这么来的。
  3. Object finalizee = this.get(); 记住了,这个是个强引用,因为都是放在unfinalized队列里面的。
  4. jla.invokeFinalize(finalizee); 调用对象 finalizee 的 finalize方法。
  5. 局部变量 finalizee = null,指针 finalizee 指向了空,方便 GC

=== 点击查看top目录 ===

4.11 clear() 方法

  • java.lang.ref.Reference#clear
    public void clear() {
        this.referent = null;
    }

解除强引用。

=== 点击查看top目录 ===

4.12 总结一下

JVM启动时,自动开启两个线程

  1. Finalizer 内部 static 块启动了FinalizerThread 线程会把 reference 从 queue 队列中再拿出来,调用他的 finalize 方法,然后再去回收。
  2. 初始化 Finalizer 的父类 Reference 的 static 块启动了 ReferenceHandler 线程会不断把 pending 队列的 reference 丢到 queue 队列,
    === 关于Reference的流程图 ===

=== 点击查看top目录 ===

五、JVM负责调用的三方法

5.1 Register方法

  • 只要发现有哪个类复写了finalize方法,那么就会进行 register

5.2 runFinalization 方法

5.2.1 Runtime#runFinalization 方法
  • java.lang.Runtime#runFinalization 系统关闭时会调用
/* Wormhole for calling java.lang.ref.Finalizer.runFinalization */
private static native void runFinalization0();

public void runFinalization() {
        runFinalization0();
    }

=== 点击查看top目录 ===

5.2.2 Finalizer.runFinalization 方法
/* Called by Runtime.runFinalization() */
    static void runFinalization() {
    	// JVM 是否启动
        if (!VM.isBooted()) {
            return;
        }
        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (;;) {
                    Finalizer f = (Finalizer)queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
            }
        });
    }
  • 把 referenceQueue 里面的 Reference 都拿出来,然后调用 finalize 方法

=== 点击查看top目录 ===

5.2.3 forkSecondaryFinalizer 方法
  • 调用了AccessController.doPrivileged方法,这个方法的作用是使其内部的代码段获得更大的权限,可以在里面访问更多的资源。
    private static void forkSecondaryFinalizer(final Runnable proc) {
        // 调用了AccessController.doPrivileged方法,这个方法的作用是使其内部的代码段获得更大的权限,可以在里面访问更多的资源。
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                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;
                }});
    }

=== 点击查看top目录 ===

5.3 runAllFinalizers 方法

5.3.1 Shutdown#runAllFinalizers 方法
  • java.lang.Shutdown#runAllFinalizers
    /* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
    private static native void runAllFinalizers();

=== 点击查看top目录 ===

5.3.2 Finalizer.runAllFinalizers 方法
/* Invoked by java.lang.Shutdown */
    static void runAllFinalizers() {
    	// JVM 是否启动
        if (!VM.isBooted()) {
            return;
        }

        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (;;) {
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }}});
    }
  • 把 unfinalized 里面的 Reference 都拿出来,然后调用 finalize 方法
  • 这里加了一把锁,因为有进有出

=== 点击查看top目录 ===

5.4 总结一下

5.4.1 runAllFinalizers 与 runFinalization 的不同
/* Called by Runtime.runFinalization() */
static void runFinalization() {
...
                for (;;) {
                    Finalizer f = (Finalizer)queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
...
    }
/* Invoked by java.lang.Shutdown */
    static void runAllFinalizers() {
...
                for (;;) {
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }
...
    }
  1. 前者从 referenceQueue 里面拿 reference,也就是把 queue 队列消耗光。后者从 unfinalized 队列拿 reference,说明是要把全部已经实例化的对象(覆盖了finalize方法的类的对象)拿出来。
  2. 注意 unfinalized 队列里面的 Reference 有可能还没有被 GC,只是登记在册而已,而queue队列里面的 reference 都是GC丢进来的了。
  3. Shutdown 打算关闭 JVM了,所以把暂时还没被GC的也一起跑到最后,就像是网吧今天停止营业了,不管你上网多久(刚上网5分钟的还有上网2小时快没钱了准备走人的),都得结账走人。

=== 点击查看top目录 ===

六、番外篇

上一章节:【JAVA Reference】Cleaner 在堆外内存DirectByteBuffer中的应用(五)

你可能感兴趣的:(引用)