6

特殊的引用-FinalReference

 4 years ago
source link: https://juejin.im/post/5e2ec11ff265da3dfa49bbaa
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
2020年01月28日 阅读 658

特殊的引用-FinalReference

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

1
可以发现,除了最熟悉的强引用没有对应的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.im/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;
        }
    }
复制代码

结合nextprev属性和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

www.infoq.cn/article/jvm…


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK