我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、前言
- 二、架构
- 三、思考 Finalizer Vs Cleaner 放一起
- 四、Finalizer 源码剖析
- 4.1 父类 FinalReference
- 4.2 Finalizer 类
- 4.3 Finalizer 类变量
- 4.4 对象初始化
- 4.5 add 方法
- 4.6 remove 方法
- 4.7 hasBeenFinalized 方法
- 4.8 static块
- 4.9 内部类 FinalizerThread
- 4.10 runFinalizer 方法
- 4.11 clear() 方法
- 4.12 总结一下
- 五、JVM负责调用的三方法
- 六、番外篇
一、前言
建议提前阅读:
- 【JAVA Reference】ReferenceQueue 与 Reference 源码剖析(二)
- 【JAVA Reference】Cleaner 源码剖析(三)
- 【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)
二、架构
2.1 代码架构
- Finalizer 继承 FinalReference 再继承 Reference。
2.2 UML流程图
三、思考 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几次,结果都一样,那么思考一下为什么?
3.2 为什么 finalize 总是比 Cleaner 先执行 ?
- 结论先抛出来: Cleaner 和 finalize 内部都有指针Pointer 指向了即将要回收的 Object 对象,但是 Cleaner 底层是虚引用(PhamtonReference),而 finalize的底层是 Finalizer ,属于强引用。所以,必须强引用的释放完对象,才轮到 Cleaner。
四、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
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;
4.3.3 next + prev 指针
// 双端指针
private Finalizer
next = null,
prev = null;
4.3.3 私有 static final 变量 lock
private static final Object lock = new Object();
- 每次对 unfinalized 队列进行 add 与 remove 的时候,都会进行加锁。
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);
}
}
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 ,表示指针指向的对象。
4.5 add 方法
往队列 unfinalized 的头部插入
// 往头部插
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
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;
}
}
4.7 hasBeenFinalized 方法
- 是否已经执行了 finalize 方法,如果 this.next = this; this.prev = this; 那么就是说明已经执行了。 因为 runFinalizer 的内部调用了 remove 方法
private boolean hasBeenFinalized() {
return (next == this);
}
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.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 方法,里面抛出的异常,是直接忽略的
}
}
}
}
4.9.1 VM.isBooted() 方法
判断 JVM 是否已经启动,如果还没启动,那么 VM.awaitBooted() 等待 JVM 启动再说。
4.9.2 SharedSecrets.getJavaLangAccess();
- setter 在 java.lang.System#setJavaLangAccess 方法内部
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方法。
4.9.3.1 注意一下 try-catch 吃掉异常
- 这个地方直接就把异常吃掉了,那么类定义的 finalize方法不管出现什么情况,都不会有异常打印出来,也不会对异常做任何处理。(即使你调用了 10 / 0),=== 具体看代码Demo#2.1.2 ===
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();
}
- 先判断是否已经执行过 finalize 方法了,是的话,就返回。所以记住,finalize方法只会执行一次。
- 调用 remove() 方法,把 next 和 prev都指向自己,所以刚刚方法hasBeenFinalized判断是否已经处理过的依据,就是这么来的。
- Object finalizee = this.get(); 记住了,这个是个强引用,因为都是放在unfinalized队列里面的。
- jla.invokeFinalize(finalizee); 调用对象 finalizee 的 finalize方法。
- 局部变量 finalizee = null,指针 finalizee 指向了空,方便 GC
4.11 clear() 方法
- java.lang.ref.Reference#clear
public void clear() {
this.referent = null;
}
解除强引用。
4.12 总结一下
JVM启动时,自动开启两个线程
- Finalizer 内部 static 块启动了FinalizerThread 线程会把 reference 从 queue 队列中再拿出来,调用他的 finalize 方法,然后再去回收。
- 初始化 Finalizer 的父类 Reference 的 static 块启动了 ReferenceHandler 线程会不断把 pending 队列的 reference 丢到 queue 队列,
=== 关于Reference的流程图 ===
五、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();
}
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 方法
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;
}});
}
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();
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 方法
- 这里加了一把锁,因为有进有出
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小时快没钱了准备走人的),都得结账走人。