美文网首页
SoftReference,WeakReference,Phan

SoftReference,WeakReference,Phan

作者: whateverblake | 来源:发表于2020-06-20 23:56 被阅读0次

    背景

    在java中引用分为四种,强引用,软引用,弱引用,虚引用。强引用是平时用到最多的,最常见的就是我们使用new去创建的对象然后赋值给到的引用就是强引用,例如Object obj = new Object(), obj就是一个强引用。对于SoftReference,WeakReference,PhantomReference 这三种类型的引用类型他们都继承自Reference这个抽象类。

    引用方式

    ReferenceQueue referenceQueue = new ReferenceQueue();
    Object obj = new Object();
    SoftReference sr = new SoftReference(obj,referenceQueue);
    WeakReference wr = new WeakReference(obj,referenceQueue);
    PhantomReference pr = new PhantomReference(obj,referenceQueue)
    
    从上面的代码我们可以看出,这三种引用提供了基本相同的使用方式,当然,对于SoftReference,WeakReference来说ReferenceQueue在初始化引用的时候不是必须的,因为如果你不提供的话,SoftReference,WeakReference的父类Reference会提供默认的实现: image.png
    image.png

    作用

    这三种引用类型引用的对象如果处于gc root链不可达,那么这些对象就有可能被随时被回收掉。对于SoftReference引用的对象来说,如果gc root链不可达,那么垃圾收集器会在内存不足的时候把它引用的对象回收掉。对于WeakReference和PhantomReference来说他们引用的对象,如果gc root链不可达那么在gc的时候会被立即回收。SoftReference和WeakReference引用的对象可以通过引用的get方法获取到相应的对象,但是对于PhantomReference来说没法通过这类引用去获取被引用的对象。当这三类引用引用的对象被回收的时候,那么这三类引用对象自身会被放在ReferenceQueue中,然后我们可以从ReferenceQueue获取相应的引用对象信息,基于这写信息我们可以做一些自己的逻辑。

    回收过程

    • Reference
      这个类是一个链表节点类型的类


      image.png
    • ReferenceHandler
      referenceHandler是Reference的内部类继承Thread,

    这个类是在Reference的静态代码块中被启动的

    static {
           ThreadGroup tg = Thread.currentThread().getThreadGroup();
           for (ThreadGroup tgn = tg;
                tgn != null;
                tg = tgn, tgn = tg.getParent());
           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();
    
           // provide access in SharedSecrets
           SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
               @Override
               public boolean tryHandlePendingReference() {
                   return tryHandlePending(false);
               }
           });
       }
    

    它的run方法如下

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

    tryHandlePending发生了什么呢

     static boolean tryHandlePending(boolean waitForNotify) {
            Reference<Object> r;
            Cleaner c;
            try {
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        // 'instanceof' might throw OutOfMemoryError sometimes
                        // so do this before un-linking 'r' from the 'pending' chain...
                        c = r instanceof Cleaner ? (Cleaner) r : null;
                        // unlink 'r' from 'pending' chain
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        // The waiting on the lock may cause an OutOfMemoryError
                        // because it may try to allocate exception objects.
                        if (waitForNotify) {
                            lock.wait();
                        }
                        // retry if waited
                        return waitForNotify;
                    }
                }
            } catch (OutOfMemoryError x) {
                // Give other threads CPU time so they hopefully drop some live references
                // and GC reclaims some space.
                // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
                // persistently throws OOME for some time...
                Thread.yield();
                // retry
                return true;
            } catch (InterruptedException x) {
                // retry
                return true;
            }
    
            // Fast path for cleaners
            if (c != null) {
                c.clean();
                return true;
            }
    
            ReferenceQueue<? super Object> q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
            return true;
        }
    

    这个是一个核心方法,如果明白这里面发生了什么,那么就能搞明白这三类引用类型引用的对象被回收后发生了什么。
    我们看到同步代码块中上来就是pending是否为空的判断,如果为空那么这个线程就去wait了,我们看下pending在Reference的定义

     private static Reference<Object> pending = null;
    

    可以看出初始化是null的,那么什么时候这个pending会被谁在什么时候赋值。
    pending会被JVM直接赋值:当着三类引用类型引用的对象被回收后那么相应的引用会被加入到pending中,pending每次只会被赋值一个引用对象,那么剩下别的引用对象会被加入到pending的discover中形成一个链表。我们可以看到在tryHandlePending每次都是处理链表上的一个reference


    image.png

    那么对每一个reference是如何处理的呢,对于reference如果是一个Cleaner那么会执行它的clean方法


    image.png
    Cleaner是PhantomReference的子类,在DirectByteBuffer中就用到了它来做堆外内存的回收,在这里就不详细介绍了,如果不是Cleaner那么就是把这个reference加入到ReferenceQueue中
    image.png
    然后我们就可以从ReferenceQueue中获取相应的reference了

    到此已经全部讲完了整个过程下面是我的实验代码

    实验

            Object obj = new Object();
            ReferenceQueue referenceQueue = new ReferenceQueue();
            WeakReference weakReference = new WeakReference(obj,referenceQueue);
            WeakReference weakReference2 = new WeakReference(obj,referenceQueue);
            WeakReference weakReference3 = new WeakReference(obj,referenceQueue);
    
            System.out.println(weakReference.get());
            System.out.println(referenceQueue.poll());
            obj = null ;
            System.gc();
            System.out.println("before isEnqueue----"+weakReference.isEnqueued());
    
            Thread.sleep(1000);
            System.out.println("after isEnqueue----"+weakReference.isEnqueued());
    
            Reference t = referenceQueue.poll() ;
            System.out.println(t);
    
            System.out.println(weakReference.get());
    

    代码比较简单,使用WeakReference做示例,new一个对象然后使用强引用obj引用它,接下来建立三个弱引用引用这个对象,这个时候我们通过weakReference.get()是可以获得这个对象的,同时通过referenceQueue.poll()是获取不到任何数据的,这个时候我们把强引用obj = null,然后通过System.gc()触发full gc,这个时候理论上新建的new Object()对象会被回收掉,然后在referenceQueue会得到执行这个对象的三个弱引用,但是因为ReferenceHandler是一个后台线程在运行,所以我们在before isEnqueue打印出的是false(reference.isEnqueue可以判断这个reference是不是被加入了ReferenceQueue中),所以我们sleep 1s等一下ReferenceQueue线程去执行,在after isEnqueue 打印出的就是true,然后我们可以通过referenceQueue.poll()获得一个weakReference,如果你poll三次那么会得到上面定义的三个weakReference。

    Tips:如果想debug tryHandlePending方法看到上面我们定义的weakReference是如何加入referenceQueue的,那么最好是把debug设置condition


    image.png

    上图是我们在idea 中设置的condition(pending instanceof WeakReference && !(pending instanceof WeakHashMap.Entry))为什么这么设置呢,因为jvm内部也有很多这种类型的引用会被回收,如果不设置就需要执行这个方法很多遍才能看到自己定义的reference

    相关文章

      网友评论

          本文标题:SoftReference,WeakReference,Phan

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