【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 的不同
- 六、番外篇
一、前言
建议提前阅读:
- 【JAVA Reference】ReferenceQueue 与 Reference 源码剖析(二)
- 【JAVA Reference】Cleaner 源码剖析(三)
- 【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(); // 把线程给跑起来}
- 启动一个内部类 FinalizerThread 线程。
- 设置优先级是 Thread.MAX_PRIORITY - 2,Cleaner 的优先级是 MAX_PRIORITY。虽然Cleaner的优先级比 FinalizerThread 高,但是由于 FinalizerThread 有强引用,所以 Cleaner 还是比较晚地执行。
- 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(); }
- 先判断是否已经执行过 finalize 方法了,是的话,就返回。所以记住,finalize方法只会执行一次。
- 调用 remove() 方法,把 next 和 prev都指向自己,所以刚刚方法hasBeenFinalized判断是否已经处理过的依据,就是这么来的。
- Object finalizee = this.get(); 记住了,这个是个强引用,因为都是放在unfinalized队列里面的。
- jla.invokeFinalize(finalizee); 调用对象 finalizee 的 finalize方法。
- 局部变量 finalizee = null,指针 finalizee 指向了空,方便 GC
=== 点击查看top目录 ===
4.11 clear() 方法
- java.lang.ref.Reference#clear
public void clear() {this.referent = null;}
解除强引用。
=== 点击查看top目录 ===
4.12 总结一下
JVM启动时,自动开启两个线程
- Finalizer 内部 static 块启动了FinalizerThread 线程会把 reference 从 queue 队列中再拿出来,调用他的 finalize 方法,然后再去回收。
- 初始化 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);}
...}
- 前者从 referenceQueue 里面拿 reference,也就是把 queue 队列消耗光。后者从 unfinalized 队列拿 reference,说明是要把全部已经实例化的对象(覆盖了finalize方法的类的对象)拿出来。
- 注意 unfinalized 队列里面的 Reference 有可能还没有被 GC,只是登记在册而已,而queue队列里面的 reference 都是GC丢进来的了。
- Shutdown 打算关闭 JVM了,所以把暂时还没被GC的也一起跑到最后,就像是网吧今天停止营业了,不管你上网多久(刚上网5分钟的还有上网2小时快没钱了准备走人的),都得结账走人。
=== 点击查看top目录 ===
六、番外篇
上一章节:【JAVA Reference】Cleaner 在堆外内存DirectByteBuffer中的应用(五)
【JAVA Reference】Finalizer 剖析 (六)相关推荐
- Java ~ Reference【总结】
前言 文章 相关系列:<Java ~ Reference[目录]>(持续更新) 相关系列:<Java ~ Reference[源码]>(学习过程/多有漏误/仅作参考/不再更新) ...
- Java ~ Reference
一 概述 在JDK1.2之前,Java中的引用的定义是十分传统的:如果引用类型的变量中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用(因此可以这样理解,所谓的引用其实就是一块保存了 ...
- Java基础学习——第六章 面向对象编程(下)
Java基础学习--第六章 面向对象编程(下) 一.关键词:static 1. static关键字的引入 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new ...
- # 20155337 2016-2017-2 《Java程序设计》第六周学习总结
20155337 2016-2017-2 <Java程序设计>第六周学习总结 教材学习内容总结 •串流(Stream): 数据有来源及目的地,衔接两者的是串流对象.如果要将数据从来源取出, ...
- java二级考试历年真题6_2018年3月计算机二级考试JAVA试题及答案(六)
2018年计算机等级考试开考在即,小编在这里为考生们整理了2018年3月计算机二级考试JAVA试题及答案,希望能帮到大家,想了解更多资讯,请关注出国留学网的及时更新哦. 2018年3月计算机二级考试J ...
- Java多线程闲聊(六):synchronized关键字
Java多线程闲聊(六):synchronized关键字 前言 这篇文章我会在博客置顶,为什么呢?因为,三篇引用的文章写得太好了,我害怕后面找不到,看不到,然后忘了! 让我想想,感觉昨天的前言把最近肚 ...
- 20155303 2016-2017-2 《Java程序设计》第六周学习总结
20155303 2016-2017-2 <Java程序设计>第六周学习总结 课堂笔记 高效学习法推荐 看视频学习(2h)→ 以代码为中心看课本,思考运行结果并验证(3h)→ 课后作业验证 ...
- java Reference
java reference Reference Java世界泰山北斗级大作<Thinking In Java>切入Java就提出"Everything is Object&qu ...
- Java开发者必备的六款工具
Java开发者必备的六款工具 摘要:对于初入行的Java开发人员来说,寻找合适的工具是困难的,并且是浪费时间的.而今天我们将列出六款Java程序员必备的工具,其中包括Notepad++.XML ...
最新文章
- tensorflow keras 上采样(放大图片) tf.keras.layers.UpSampling2D 示例
- Paper:《First Order Motion Model for Image Animation》翻译与解读
- W: 无法下载 http://ppa.launchpad.net/fcitx-team/nightly/ubuntu/dists/jessie/main/binary-amd64/Packages
- 深入分析Php处理浮点数的问题
- LongCache机制与Long等值比较\\\\Integer 中的缓存类IntegerCache
- 第一章:AJAX与jQuery
- Java国家/地区使用限制条款引发争议
- 灰色关联法 —— matlab
- MySQL_日期时间处理函数及应用
- 孩子教育经验,自己整理,不看后悔一辈子
- LG新能源新设首席数字官 首任是英伟达前数据科学家
- Centos系统设置
- 2019蓝桥杯 - 迷宫
- Hexo主题next中添加天气插件(心知天气)
- cv2.imshow的问题
- 心血来潮:重新温习一下C语言的指针
- [python3.6]爬虫实战之爬取淘女郎图片
- Explorer怪病
- 《工程伦理与学术道德》之《工程与伦理》
- 【Linux】VIM命令(全面详解)
热门文章
- Caffe Prototxt 特殊层系列:Crop Layer
- Diagnostic Viewer 显示空白
- Linux kernel Panic后自动重启机器的设置
- 三菱fx1n40mr001接线图_三菱FX1N-40MR-001使用说明书 - 广州凌控
- 抖音快手如何快速涨粉技巧整理
- 在线订票系统--永胜票务网是怎么搭建的?
- javaweb成语接龙
- oracle修改mem为手动管理,PSV内存修改金手指插件GoHANmem v2.00下载和使用教程
- Java+Netty+WebRTC、语音、视频、屏幕共享【聊天室设计实践】
- Lory Carousel滑块具有CSS动画和触摸支持