特殊的引用-FinalReference

大家都知道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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容

  • JDK1.2之后,Java扩充了引用的概念,将引用分为强引用、软引用、弱引用和虚引用四种。 强引用类似于”Obje...
    lesline阅读 4,880评论 0 0
  • 我希望通过这篇文章能够把Android内存相关的基础和大部分内存相关问题如:溢出、泄漏、图片等等产生的都讲解清楚,...
    Cactus_b245阅读 7,164评论 6 82
  • 转自:https://yq.aliyun.com/articles/2947?spm=0.0.0.0.At14xp...
    YDDMAX_Y阅读 566评论 0 0
  • 旧词添新愁, 红尘几时休? 年华不慎漏, 莫叹时光流。 这几天收益少了,看来是更新少的原因,今天晚上多发几篇吧。
    雪夜挽风阅读 227评论 0 2
  • 伴随着阵阵烟花声,结束了新年的庆祝。千家万户,阖家欢乐。 此时的我,没有了往年那样的热情,只有期盼。我会想,你在干...
    兴妮儿阅读 182评论 0 0