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 extends Reference {

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

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

Finalizer.PNG

二、Finalizer

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

    private static ReferenceQueue 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 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 {
        static private class Lock { }
        private static Lock lock = new Lock();
    
        private static Reference 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 r;
            ...
            if (pending != null) {
            ...
            } else {
                if (waitForNotify) { lock.wait(); }
                return waitForNotify;
            }
            ...
            ReferenceQueue 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和FinalReference)