java学习——源码分析finalize和FinalReference

  • 一道常见的java面试题:描述final、finally、finalize的区别
    final、finally是常用的java关键字,不赘述。
    finalize是Object类的方法名,如果重写了finalize方法,jvm在这个对象被gc之前会执行对象的finalize方法。

  • java的引用常见的有强引用、软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference),而FinalReference同样继承了Reference类,但在编程时从未用到过。

一、FinalReference

class FinalReference<T> extends Reference<T> {

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

FinalReference继承了Reference,类访问权限是package,我们无法继承扩展,jdk对此类进行了扩展实现java.lang.ref.Finalizer

Finalizer.PNG

二、Finalizer

final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
                                                          same package as the Reference
                                                          class */

    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 Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }

    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

...

Finalizer的类访问权限也是package,而且是final不能继承。

  1. Finalizer的属性
  • static ReferenceQueue<Object> queue,Finalizer引用的对象被gc之前,jvm会把相应的Finalizer对象放入队列。
  • static Finalizer unfinalized,静态的Finalizer对象链。
  • Finalizer next = null, prev = null,对象链上一个、下一个的引用
  1. Finalizer构造函数
  • private私有构造函数,我们无法自己创建Finalizer类的对象。
  • 参数finalizee,FinalReference引用的对象。
  • 构造方法会调用add(),把当前对象加入Finalizer对象链。

三、注册Finalizer对象

    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

由于构造函数是私有的,外部无法调用,只有jvm调用Finalizer.register(finalizee)时才会创建Finalizer对象并加入对象链。

  • f类:如果一个类重写了void finalize()方法,并且方法体不为空,类加载时jvm会给这个类加上标记,表示这是一个finalizer类(为和Finalizer类区分,以下都叫f类)。
  • 创建一个对象,会先为分配对象空间,然后调用构造方法。
  • 如果创建的是f类对象,默认会在调用构造方法返回之前调用register方法,参数就是当前对象。如果设置了-XX:-RegisterFinalizersAtInit,则会在调用构造方法之前调用register方法。
  • clone一个f类对象,会在clone完成时调用register方法。

四、加入ReferenceQueue等待gc回收

public abstract class Reference<T> {
    static private class Lock { }
    private static Lock lock = new Lock();

    private static Reference<Object> pending = null;

    private static class ReferenceHandler extends Thread {
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
        ...
    }

    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        ...
        if (pending != null) {
        ...
        } else {
            if (waitForNotify) { lock.wait(); }
            return waitForNotify;
        }
        ...
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

    static {
        ...
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        ...
    }
}
  • gc发生时,gc算法会判断对象是否只被Finalizer类引用了(f类对象被Finalizer对象引用,然后放到Finalizer对象链里),
    如果是,jvm会把Finalizer对象赋给Referencepending属性,并调用lock.notify()
  • Reference的静态块会创建一个守护线程ReferenceHandler,循环执行tryHandlePending方法
  • tryHandlePending方法执行时,如果pending为空,会调用lock.wait(),释放锁对象并让线程进入阻塞状态。
  • 一旦jvm给pending赋值并调用了lock.notify(),ReferenceHandler线程将被唤醒,将Finalizer对象加入ReferenceQueue。

五、f类对象的GC回收

  private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            if (running)
                return;
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer();
                } catch (InterruptedException x) {
                    continue;
                }
            }
        }
    }

    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();
    }

FinalizerThreadFinalizer的内部类,继承了Thread
Finalizer的静态块中,会创建一个守护线程FinalizerThread,run方法会循环从ReferenceQueue中取出Finalizer对象,执行runFinalizer方法

private void runFinalizer() {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                invokeFinalizeMethod(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();
    }

 static native void invokeFinalizeMethod(Object o) throws Throwable;

runFinalizer方法会将对象传递给本地方法invokeFinalizeMethod(),最终调用f类对象自身的finalize()

以上就是对象被回收之前jvm执行finalize方法的全过程,其中对多线程的使用值得借鉴。
下图显示了此过程中参与的类,红色带+号的线表示内部类

FinalReference.PNG

六、Finalizer导致的内存泄露

假如某个类想通过finalize方法,来防止类被使用后忘记释放资源,那么对象至少会在第二次gc时才能被回收,
所以不应在运行期创建大量f类对象,容易导致内存泄漏。

  • Finalizer其实是实现了析构函数的概念,我们在对象被回收前可以执行一些『收拾性』的逻辑,但也给对象生命周期和gc带来了影响。
  • f类对象因为Finalizer的引用而变成了一个临时的强引用,无法被立即回收。
  • f类对象只有在FinalizerThread执行完finalize()后的下一次gc才能被回收,这期间可能经历多次gc了。
  • cpu资源比较稀缺的情况下,FinalizerThread线程有可能因为优先级较低而延迟执行f类对象的finalize()。
  • 因为f类对象的finalize()迟迟没有执行,有可能会导致大部分f对象进入到老年代,引发老年代gc甚至fullgc,gc暂停时间明显变长。

参考资料
https://mp.weixin.qq.com/s/OVtGfivZxBt8Ht2yZ8rccg
https://www.jianshu.com/p/65369496d0b6

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

推荐阅读更多精彩内容