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

文章目录

  • 一、前言
  • 二、架构
    • 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 代码架构


  • Finalizer 继承 FinalReference 再继承 Reference。

2.2 UML流程图

=== 点击查看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@ToStringstatic class DemoObject {private String name;@Overrideprotected 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@Overridepublic 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

=== 点击查看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 Finalizernext = 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.1while (!VM.isBooted()) {// delay until VM completes initialization// 延迟等待 JVM 完全初始化完毕try {VM.awaitBooted();} catch (InterruptedException x) {// ignore and continue}}// 4.9.2final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // getter 直接看 Reference 类的 static 代码块 running = true;// 4.9.3for (;;) {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 decreasethe 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中的应用(五)

【JAVA Reference】Finalizer 剖析 (六)相关推荐

  1. Java ~ Reference【总结】

    前言 文章 相关系列:<Java ~ Reference[目录]>(持续更新) 相关系列:<Java ~ Reference[源码]>(学习过程/多有漏误/仅作参考/不再更新) ...

  2. Java ~ Reference

    一 概述 在JDK1.2之前,Java中的引用的定义是十分传统的:如果引用类型的变量中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用(因此可以这样理解,所谓的引用其实就是一块保存了 ...

  3. Java基础学习——第六章 面向对象编程(下)

    Java基础学习--第六章 面向对象编程(下) 一.关键词:static 1. static关键字的引入 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new ...

  4. # 20155337 2016-2017-2 《Java程序设计》第六周学习总结

    20155337 2016-2017-2 <Java程序设计>第六周学习总结 教材学习内容总结 •串流(Stream): 数据有来源及目的地,衔接两者的是串流对象.如果要将数据从来源取出, ...

  5. java二级考试历年真题6_2018年3月计算机二级考试JAVA试题及答案(六)

    2018年计算机等级考试开考在即,小编在这里为考生们整理了2018年3月计算机二级考试JAVA试题及答案,希望能帮到大家,想了解更多资讯,请关注出国留学网的及时更新哦. 2018年3月计算机二级考试J ...

  6. Java多线程闲聊(六):synchronized关键字

    Java多线程闲聊(六):synchronized关键字 前言 这篇文章我会在博客置顶,为什么呢?因为,三篇引用的文章写得太好了,我害怕后面找不到,看不到,然后忘了! 让我想想,感觉昨天的前言把最近肚 ...

  7. 20155303 2016-2017-2 《Java程序设计》第六周学习总结

    20155303 2016-2017-2 <Java程序设计>第六周学习总结 课堂笔记 高效学习法推荐 看视频学习(2h)→ 以代码为中心看课本,思考运行结果并验证(3h)→ 课后作业验证 ...

  8. java Reference

    java reference Reference Java世界泰山北斗级大作<Thinking In Java>切入Java就提出"Everything is Object&qu ...

  9. Java开发者必备的六款工具

     Java开发者必备的六款工具 摘要:对于初入行的Java开发人员来说,寻找合适的工具是困难的,并且是浪费时间的.而今天我们将列出六款Java程序员必备的工具,其中包括Notepad++.XML ...

最新文章

  1. tensorflow keras 上采样(放大图片) tf.keras.layers.UpSampling2D 示例
  2. Paper:《First Order Motion Model for Image Animation》翻译与解读
  3. W: 无法下载 http://ppa.launchpad.net/fcitx-team/nightly/ubuntu/dists/jessie/main/binary-amd64/Packages
  4. 深入分析Php处理浮点数的问题
  5. LongCache机制与Long等值比较\\\\Integer 中的缓存类IntegerCache
  6. 第一章:AJAX与jQuery
  7. Java国家/地区使用限制条款引发争议
  8. 灰色关联法 —— matlab
  9. MySQL_日期时间处理函数及应用
  10. 孩子教育经验,自己整理,不看后悔一辈子
  11. LG新能源新设首席数字官 首任是英伟达前数据科学家
  12. Centos系统设置
  13. 2019蓝桥杯 - 迷宫
  14. Hexo主题next中添加天气插件(心知天气)
  15. cv2.imshow的问题
  16. 心血来潮:重新温习一下C语言的指针
  17. [python3.6]爬虫实战之爬取淘女郎图片
  18. Explorer怪病
  19. 《工程伦理与学术道德》之《工程与伦理》
  20. 【Linux】VIM命令(全面详解)

热门文章

  1. Caffe Prototxt 特殊层系列:Crop Layer
  2. Diagnostic Viewer 显示空白
  3. Linux kernel Panic后自动重启机器的设置
  4. 三菱fx1n40mr001接线图_三菱FX1N-40MR-001使用说明书 - 广州凌控
  5. 抖音快手如何快速涨粉技巧整理
  6. 在线订票系统--永胜票务网是怎么搭建的?
  7. javaweb成语接龙
  8. oracle修改mem为手动管理,PSV内存修改金手指插件GoHANmem v2.00下载和使用教程
  9. Java+Netty+WebRTC、语音、视频、屏幕共享【聊天室设计实践】
  10. Lory Carousel滑块具有CSS动画和触摸支持