大家都知道java里面引用有SoftReference、WeakReference、PhantomReference,他们都继承自抽象类Reference,我们看一下他的类图:
可以发现,除了最熟悉的强引用没有对应的Reference实现外,虚引用,弱引用和软引用都有对应的Reference实现类。
那么,多出来的FinalReference实现是干什么的呢?
FinalReference
可以看到,FinalReference类仅仅是继承了Reference类而已。
/** * Final references, used to implement finalization */
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
注释中说他是用来实现finalization(终结)的。
其真正的逻辑位于FinalReference的唯一子类:java.lang.ref.Finalizer
中。
注意,该类为包级私有,有final关键字修饰,且构造方法为private,提供了register方法供JVM调用。
构造方法以及register方法
final class Finalizer extends FinalReference<Object> {
//...
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
}
因为register方法并没有返回值,所以在外部是无法获取到创建的Finalizer对象。 其中,构造方法中调用的super(finalizee, queue)
会将入参finalizee加入到引用队列queue
中。
关于引用队列,见juejin.cn/post/684490…
我们分析的转入构造方法中所调用的add方法。
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
private static final Object lock = new Object();
private Finalizer
next = null,
prev = null;
//...
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
结合next
,prev
属性和add
方法,可以比较容易的看出unfinalized
实际上是一个双向链表,在add
方法被调用后,就会将当前对象加入到unfinalized
链表。
其实,在构造方法
方法被调用后,实际上做了如下两件事:
- 调用super,将入参对象注册至引用队列。
- 调用add方法,将当前创建对象加入
unfinalized
链表。
因为register
方法并没有返回值,且unfinalized
属性为静态成员变量,所以当前创建对象在虚拟机内仅该unfinalized
链表持有一份引用
根据注释和访问规则来看,register
方法仅会被虚拟机所调用,而且,只有重写了java.lang.Object#finalize
方法的类才会被作为参数调用Finalizer#register
方法。
后台线程
与pending handler类似,在FinalReference中同样也是使用静态代码块来启动后台线程。
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);
finalizer.setDaemon(true);
finalizer.start();
}
看一下FinalizerThread
类,该类继承了Thread类,并重写run
方法。
private static class FinalizerThread extends Thread {
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 (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
上面代码段中仅保留了关键流程代码。
可以看出在run方法内使用了一个死循环,每次循环先将队首元素从引用队列中取出(在构造方法内将对象注册至引用队列,当引用状态变为pending时,会由Pending-handler-thread将其加入该注册队列),并执行runFinalizer
方法。
继续看runFinalizer
方法:
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
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) { }
super.clear();
}
根据方法名就可以看出来,该方法的作用就是执行jla.invokeFinalize(finalizee)
,并执行一些清理操作。
而在JavaLangAccess
的实现类中(java.lang.System中的一个匿名内部类),invokeFinalize
的代码也非常简单,只是调用finalize
方法。
public void invokeFinalize(Object o) throws Throwable {
o.finalize();
}
在invokeFinalize
之后,代码中去主动将finalizee设置为null,根据上面的注释可知,是为了清除该方法对当前对象的引用,减小影响gc的概率。
在执行finalizee方法时,该对象会被临时加一个强引用,进而对gc产生影响
finalize方法
从上面的分析过程可以看出java.lang.Object
中的finalize
方法在对象将要被回收的时由一个守护线程去调用他们的finalize
方法。
由于该线程的优先级并不能保证,在准备调用finalize
方法到调用结束时,可能已经经过了多次gc,而由于临时的强引用,导致该对象迟迟没有被回收。
但是,finalize
的调用并不能被保证。所以,该方法在java9已被标记为过时。我们也不应该去重写该方法去做清理工作。
总结
其实FinalReference就是jdk为了将Finalizer方法实现类似析构方法而打造的类。
由虚拟机先将重写了Finalizer方法的对象注册至引用队列,暂存在链表中。
当对象引用状态变为Enqueued后,由守护线程从引用队列中取出对象,建立临时的强引用,并调用Finalizer方法。
由于守护线程的优先级较低,并不能保证重写的Finalizer方法在被回收前一定会被执行。并且因为有临时强引用的存在,还可能使该对象错过gc。
所以,并不应该使用Finalizer方法~
参考资料
jdk8源码&doc
今天的文章特殊的引用-FinalReference分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16640.html