美文网首页Java
特殊的引用-FinalReference

特殊的引用-FinalReference

作者: Java天天 | 来源:发表于2020-02-20 15:47 被阅读0次

    大家都知道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.im/post/5e19d6…

    我们分析的转入构造方法中所调用的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方法~

    最后:

    上面都是自己整理好的!我就把资料贡献出来给有需要的人!顺便求一波关注,哈哈~各位小伙伴关注我后私信【Java】就可以免费领取哒

    作者:一缕阳光同志

    链接:https://juejin.im/post/5e2ec11ff265da3dfa49bbaa

    相关文章

      网友评论

        本文标题:特殊的引用-FinalReference

        本文链接:https://www.haomeiwen.com/subject/bhpxqhtx.html