美文网首页
java学习——源码分析finalize和FinalRefere

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

作者: 高稷 | 来源:发表于2018-07-02 15:23 被阅读0次
    • 一道常见的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

    相关文章

      网友评论

          本文标题:java学习——源码分析finalize和FinalRefere

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