特殊的引用-FinalReference

你们都知道java里面引用有SoftReference、WeakReference、PhantomReference,他们都继承自抽象类Reference,咱们看一下他的类图: java

能够发现,除了最熟悉的强引用没有对应的Reference实现外,虚引用,弱引用和软引用都有对应的Reference实现类。

那么,多出来的FinalReference实现是干什么的呢? bash

FinalReference

能够看到,FinalReference类仅仅是继承了Reference类而已。jvm

/**
 * Final references, used to implement finalization
 */
class FinalReference<T> extends Reference<T> {

    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
复制代码

注释中说他是用来实现finalization(终结)的。post

其真正的逻辑位于FinalReference的惟一子类:java.lang.ref.Finalizer中。ui

注意,该类为包级私有,有final关键字修饰,且构造方法为private,提供了register方法供JVM调用。this

构造方法以及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中。spa

关于引用队列,见juejin.im/post/5e19d6…线程

咱们分析的转入构造方法中所调用的add方法。code

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;
        }
    }
复制代码

结合nextprev属性和add方法,能够比较容易的看出unfinalized其实是一个双向链表,在add方法被调用后,就会将当前对象加入到unfinalized链表。cdn

其实,在构造方法方法被调用后,实际上作了以下两件事:

  • 调用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

www.infoq.cn/article/jvm…

相关文章
相关标签/搜索