java Reference源码解析

Reference源码

/**
 *  引用对象(reference objects)的抽象基类。
 *  此类定义了所有引用对象的抽象操作。
 *  因为这个类是在与garbage collector 紧密合作的情况下实现的,所以此类不能被子类化(就是 我们无法自定义一个类用于继承这个类)。
 *
 */

public abstract class Reference<T> {
    
    

    /* A reference instance 处于四种可能的内部状态之一:

     *  Active(活动状态):
     *      一个引用对象,并且状态是Active,就会被garbage collector 特殊对待.
     *      垃圾收集器在检测到 引用对象的可到达性 已经被更改为适当的状态后(实际对象处于 GC roots 不可达),
     *      会根据实例在创建时是否向队列注册了实例,将实例的状态更改为“Pending” 或者 “Inactive”。
     *
     *      如果 Reference 注册了ReferenceQueue(通俗的说,就是new Reference时,传入了一个ReferenceQueue参数),则会切换为Pending,并且Reference会加入pending-Reference链表中,
     *      如果没有注册ReferenceQueue,会切换为Inactive
     *
     *      新创建的实例处于活动状态。
     *
     *  Pending(挂起状态):
     *      等待被Reference-handler线程加入ReferenceQueue队列的  pending-reference链表中的 元素,都是处于此状态
     *
     *  Enqueued:
     *      ReferenceQueue队列中的Reference的状态
     *      当一个实例从其ReferenceQueue中删除时,它就变成Inactive的。未注册的实例永远不会处于此状态(就是new时,没有传入ReferenceQueue参数的实例 永远不会处于此状态)。
     *
     *  Inactive(不活动状态):
     *      不做任何操作。一旦实例处于此状态,那么他的状态再也不会发生改变。
     *      同时说明该引用实例所指向的实际对象一定会被GC所回收
     *
     * 每个状态的队列 以及 next对象如下所示
     *
     *     Active:
     *          queue = ReferenceQueue with which instance is registered, or ReferenceQueue.NULL if it was not registered with a queue;
     *          next = null.
     *
     *     Pending:
     *          queue = ReferenceQueue with which instance is registered;
     *          next = this
     *
     *     Enqueued:
     *          queue = ReferenceQueue.ENQUEUED;
     *          next = Following instance in queue, or this if at end of list.
     *
     *     Inactive: queue = ReferenceQueue.NULL; next = this.
     *
     * 通过使用这种方式,垃圾收集器 只需要检查 next字段就能决定,这个引用实例是否需要特殊处理。
     * 如果next字段是null,那么这个实例处于Active状态
     * 如果next字段非null,收集器正常处理这个实例
     *
     */

    /**
     * 要搞清楚,referent 和 Reference 的区别。referent是目标对象,Reference只是对referent的“包装”。
     * referent被Reference“包装后”,就会被Garbage Collector特殊处理。
     * 特殊之处在于:gc时,Garbage Collector 会 “优先” 回收 referent对象,要求更严格一点。
     *      软引用:Garbage Collector 会在堆内存不够时,回收referent对象(即使Gc Root可达referent对象)
     *      弱引用:Garbage Collector 每次gc,都会回收referent对象(即使Gc Root可达referent对象)
     *      而普通的引用,无论内存够不够,只要Gc Root可达,都不会回收
     *
     */
    private T referent;         /* Treated specially by GC */

    /**
     * referent对象被回收后,此时referent=null,但是 Reference!=null。
     * 如果new Reference时,传入ReferenceQueue参数,referent被回收后,当前Reference实例会被添加到这个队列中。Reference加入到ReferenceQueue后, Reference的queue就会被设置为ReferenceQueue.ENQUEUED,来标识当前Reference已经进入到队里里面了;
     * 如果没有传入ReferenceQueue参数,Reference会直接被回收掉
     *
     * 实际上ReferenceQueue 只有一个Reference head,通过 head.next -> head.next.next -> head.next.next.next 形成一个queue
     *
     */
    volatile ReferenceQueue<? super T> queue;

    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    //当前Reference实例的“下一个”,ReferenceQueue基于此字段实现的
    @SuppressWarnings("rawtypes")
    volatile java.lang.ref.Reference next;



    /**
     * 整个jvm中唯一,与discovered结合使用,pending 与 discovered构成了一个链表,这个链表就是pending-Reference链表,链表中存放了 需要加入ReferenceQueue队列的元素
     *
     * Garbage Collector 回收了referent对象后,会把相应的Reference对象放入pending-Reference链表中
     *
     * pending-Reference链表: pending -> pending.discovered -> pending.discovered.discovered -> pending.discovered.discovered.discovered
     * Reference-handler线程 把 pending 入队到 ReferenceQueue,入队后 r = pending; pending = r.discovered; r.discovered = null;
     *
     * 进入pending-Reference链表,Reference对象需要满足两个条件
     *  1. Reference所引用的对象已经不存在其它强引用;
     *  2. Reference对象在创建的时候,指定了ReferenceQueue;
     */
    private static java.lang.ref.Reference<Object> pending = null;

    //由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是pending-reference链表的下一个元素,通过JVM直接调用赋值
    transient private Reference<T> discovered;  /* used by VM */


    /**
     * Reference-handler线程需要同步操作,Lock用于提供 synchronized关键字所需要的锁对象
     */
    static private class Lock {
    
     }
    private static Lock lock = new Lock();




    /* High-priority thread to enqueue pending References
     */

    /**
     * 这就是Reference-handler线程了,作用就是 把pending-Reference链表中的Reference 视情况加入到 ReferenceQueue 或者 丢弃
     */
    private static class ReferenceHandler extends Thread {
    
    

        private static void ensureClassInitialized(Class<?> clazz) {
    
    
            try {
    
    
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
    
    
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
    
    
            // pre-load and initialize InterruptedException and Cleaner classes
            // so that we don't get into trouble later in the run loop if there's
            // memory shortage while loading/initializing them lazily.
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
    
    
            super(g, name);
        }

        public void run() {
    
    
            while (true) {
    
     // 不停调用tryHandlePending,用于将pending-Reference链表的元素加入到ReferenceQueue
                tryHandlePending(true);
            }
        }
    }

    /**
     * Try handle pending {@link java.lang.ref.Reference} if there is one.<p>
     * Return {@code true} as a hint that there might be another
     * {@link java.lang.ref.Reference} pending or {@code false} when there are no more pending
     * {@link java.lang.ref.Reference}s at the moment and the program can do some other
     * useful work instead of looping.
     *
     * @param waitForNotify if {@code true} and there was no pending
     *                      {@link java.lang.ref.Reference}, wait until notified from VM
     *                      or interrupted; if {@code false}, return immediately
     *                      when there is no pending {@link java.lang.ref.Reference}.
     * @return {@code true} if there was a {@link java.lang.ref.Reference} pending and it
     *         was processed, or we waited for notification and either got it
     *         or thread was interrupted before being notified;
     *         {@code false} otherwise.
     */
    static boolean tryHandlePending(boolean waitForNotify) {
    
    
        java.lang.ref.Reference<Object> r;
        Cleaner c;
        try {
    
    
            synchronized (lock) {
    
     // 此处,进行同步操作,因为不仅ReferenceHandler线程会操作 pending-Reference链表(删除),gc线程也会操作pending-Reference链表(添加)
                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;
    }

    static {
    
    
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new java.lang.ref.Reference.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);
            }
        });
    }

    /* -- Referent accessor and setters -- */

    /**
     * Returns this reference object's referent.  If this reference object has
     * been cleared, either by the program or by the garbage collector, then
     * this method returns <code>null</code>.
     *
     * @return   The object to which this reference refers, or
     *           <code>null</code> if this reference object has been cleared
     */
    public T get() {
    
    
        return this.referent;
    }

    /**
     * Clears this reference object.  Invoking this method will not cause this
     * object to be enqueued.
     *
     * <p> This method is invoked only by Java code; when the garbage collector
     * clears references it does so directly, without invoking this method.
     */
    public void clear() {
    
    
        this.referent = null;
    }


    /* -- Queue operations -- */

    /**
     * Tells whether or not this reference object has been enqueued, either by
     * the program or by the garbage collector.  If this reference object was
     * not registered with a queue when it was created, then this method will
     * always return <code>false</code>.
     *
     * @return   <code>true</code> if and only if this reference object has
     *           been enqueued
     */
    public boolean isEnqueued() {
    
    
        return (this.queue == ReferenceQueue.ENQUEUED);
    }

    /**
     * Adds this reference object to the queue with which it is registered,
     * if any.
     *
     * <p> This method is invoked only by Java code; when the garbage collector
     * enqueues references it does so directly, without invoking this method.
     *
     * @return   <code>true</code> if this reference object was successfully
     *           enqueued; <code>false</code> if it was already enqueued or if
     *           it was not registered with a queue when it was created
     */
    public boolean enqueue() {
    
    
        return this.queue.enqueue(this);
    }


    /* -- Constructors -- */

    Reference(T referent) {
    
    
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
    
    
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

}

Reference的状态变化如下图所示:
在这里插入图片描述
总结:把一个普通的引用变为一个特殊的引用,Garbage Collector特殊处理这个引用,并且在回收这个引用后,把这个引用的

ReferenceQueue源码

ReferenceQueue源码 就不一一解读了,这个很简单,主要关注enqueue方法

public class ReferenceQueue<T> {
    
    

    /**
     * Constructs a new reference-object queue.
     */
    public ReferenceQueue() {
    
     }

    private static class Null<S> extends ReferenceQueue<S> {
    
    
        boolean enqueue(Reference<? extends S> r) {
    
    
            return false;
        }
    }

    static ReferenceQueue<Object> NULL = new Null<>();
    static ReferenceQueue<Object> ENQUEUED = new Null<>();

    static private class Lock {
    
     };
    private Lock lock = new Lock();
    private volatile Reference<? extends T> head = null;
    private long queueLength = 0;

    boolean enqueue(Reference<? extends T> r) {
    
     /* Called only by Reference class */
        synchronized (lock) {
    
    
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
    
    
                return false;
            }
            assert queue == this;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
    
    
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }

    private Reference<? extends T> reallyPoll() {
    
           /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
    
    
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            head = (rn == r) ? null : rn;
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
    
    
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

    /**
     * Polls this queue to see if a reference object is available.  If one is
     * available without further delay then it is removed from the queue and
     * returned.  Otherwise this method immediately returns <tt>null</tt>.
     *
     * @return  A reference object, if one was immediately available,
     *          otherwise <code>null</code>
     */
    public Reference<? extends T> poll() {
    
    
        if (head == null)
            return null;
        synchronized (lock) {
    
    
            return reallyPoll();
        }
    }

    /**
     * Removes the next reference object in this queue, blocking until either
     * one becomes available or the given timeout period expires.
     *
     * <p> This method does not offer real-time guarantees: It schedules the
     * timeout as if by invoking the {@link Object#wait(long)} method.
     *
     * @param  timeout  If positive, block for up to <code>timeout</code>
     *                  milliseconds while waiting for a reference to be
     *                  added to this queue.  If zero, block indefinitely.
     *
     * @return  A reference object, if one was available within the specified
     *          timeout period, otherwise <code>null</code>
     *
     * @throws  IllegalArgumentException
     *          If the value of the timeout argument is negative
     *
     * @throws  InterruptedException
     *          If the timeout wait is interrupted
     */
    public Reference<? extends T> remove(long timeout)
        throws IllegalArgumentException, InterruptedException
    {
    
    
        if (timeout < 0) {
    
    
            throw new IllegalArgumentException("Negative timeout value");
        }
        synchronized (lock) {
    
    
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
            long start = (timeout == 0) ? 0 : System.nanoTime();
            for (;;) {
    
    
                lock.wait(timeout);
                r = reallyPoll();
                if (r != null) return r;
                if (timeout != 0) {
    
    
                    long end = System.nanoTime();
                    timeout -= (end - start) / 1000_000;
                    if (timeout <= 0) return null;
                    start = end;
                }
            }
        }
    }

    /**
     * Removes the next reference object in this queue, blocking until one
     * becomes available.
     *
     * @return A reference object, blocking until one becomes available
     * @throws  InterruptedException  If the wait is interrupted
     */
    public Reference<? extends T> remove() throws InterruptedException {
    
    
        return remove(0);
    }

    /**
     * Iterate queue and invoke given action with each Reference.
     * Suitable for diagnostic purposes.
     * WARNING: any use of this method should make sure to not
     * retain the referents of iterated references (in case of
     * FinalReference(s)) so that their life is not prolonged more
     * than necessary.
     */
    void forEach(Consumer<? super Reference<? extends T>> action) {
    
    
        for (Reference<? extends T> r = head; r != null;) {
    
    
            action.accept(r);
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            if (rn == r) {
    
    
                if (r.queue == ENQUEUED) {
    
    
                    // still enqueued -> we reached end of chain
                    r = null;
                } else {
    
    
                    // already dequeued: r.queue == NULL; ->
                    // restart from head when overtaken by queue poller(s)
                    r = head;
                }
            } else {
    
    
                // next in chain
                r = rn;
            }
        }
    }
}

Reference 实例演示

public class Apple {
    
    

    private String name;

    public Apple(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    /**
     * 覆盖finalize,在回收的时候会执行。
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
    
    
        super.finalize();
        System.out.println("Apple: " + name + " finalize。");
    }

    @Override
    public String toString() {
    
    
        return "Apple{" +
                "name='" + name + '\'' +
                '}' + ", hashCode:" + this.hashCode();
    }
}
public class WeakReferenceTest2 {
    
    
    public static void main(String[] args) {
    
    
        ReferenceQueue<Apple> appleReferenceQueue = new ReferenceQueue<>();
        WeakReference<Apple> appleWeakReference1 = new WeakReference<Apple>(new Apple("青苹果"), appleReferenceQueue);
        WeakReference<Apple> appleWeakReference2 = new WeakReference<Apple>(new Apple("毒苹果"), appleReferenceQueue);
        WeakReference<Apple> appleWeakReference3 = new WeakReference<Apple>(new Apple("红苹果"), appleReferenceQueue);
        WeakReference<Apple> appleWeakReference4 = new WeakReference<Apple>(new Apple("黄苹果"), appleReferenceQueue);

        System.out.println("=====gc调用前=====");
        Reference<? extends Apple> reference = null;
        while ((reference = appleReferenceQueue.poll()) != null ) {
    
    
            //不会输出,因为没有回收被弱引用的对象,并不会加入队列中
            System.out.println(reference);
        }
        System.out.println(appleWeakReference1);
        System.out.println(appleWeakReference2);
        System.out.println(appleWeakReference3);
        System.out.println(appleWeakReference4);
        System.out.println(appleWeakReference1.get());
        System.out.println(appleWeakReference2.get());
        System.out.println(appleWeakReference3.get());
        System.out.println(appleWeakReference4.get());

        System.out.println("=====调用gc=====");
        System.gc();
        try {
    
    
            Thread.sleep(5000*1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println("=====gc调用后=====");

        //下面两个输出为null,表示对象被回收了
        System.out.println(appleWeakReference1.get());
        System.out.println(appleWeakReference2.get());
        System.out.println(appleWeakReference3.get());
        System.out.println(appleWeakReference4.get());

        //输出结果,并且就是上面的appleWeakReference、appleWeakReference2,再次证明对象被回收了
        Reference<? extends Apple> reference2 = null;
        while ((reference2 = appleReferenceQueue.poll()) != null ) {
    
    
            //如果使用继承的方式就可以包含其他信息了
            System.out.println("appleReferenceQueue中:" + reference2);
        }
    }
}

首先我们看下未调用gc前,各个Reference的状态:
在这里插入图片描述
调用System.gc(),触发gc,4个Reference的referent对象都会被回收掉,所以referent都为null。如下图所示
在这里插入图片描述

因为在构造4个Reference时,传入了ReferenceQueue,如下图
在这里插入图片描述
所以在referent被回收后,appleWeakReference1、appleWeakReference2、 appleWeakReference3、 appleWeakReference4的状态变为pending,并且这4个Reference全部加入到pending-Reference链表(加入这一步骤由gc线程完成)。我们看下pending-Reference链表,如下图:
在这里插入图片描述
接下来,我们看下,Reference-handler线程如何将pending-Reference链表中的元素加入到ReferenceQueue,如下图:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
此时,就完成了appleWeakReference4(WeakReference@624)的入队操作。我们来看下,此时appleWeakReference4以及ReferenceQueue的状态,如下图
在这里插入图片描述至此为止,appleWeakReference4处理成功。接下来再看下appleWeakReference3(WeakReference@623)的处理,如下图
在这里插入图片描述
appleWeakReference3处理完成后,我们看下,appleWeakReference3、appleWeakReference4以及ReferenceQueue的状态,如下图:
在这里插入图片描述
接下来appleWeakReference2,以及appleWeakReference1都是重复同样的操作,这里就不重复列举。
最终,这4个Reference全部都会加入到ReferenceQueue,也就是我们之前传入的appleReferenceQueue。我们通过appleReferenceQueue的head(Reference类型),以及head的next,得到4个Reference。
此处,贴下程序的执行结果:

=====gc调用前=====
java.lang.ref.WeakReference@61443d8f
java.lang.ref.WeakReference@445b84c0
java.lang.ref.WeakReference@61a52fbd
java.lang.ref.WeakReference@233c0b17
Apple{
    
    name='青苹果'}, hashCode:1674896058
Apple{
    
    name='毒苹果'}, hashCode:2075203460
Apple{
    
    name='红苹果'}, hashCode:866191240
Apple{
    
    name='黄苹果'}, hashCode:1879492184
=====调用gc=====
Apple: 红苹果 finalize。
Apple: 黄苹果 finalize。
Apple: 毒苹果 finalize。
Apple: 青苹果 finalize。
=====gc调用后=====
null
null
null
null
appleReferenceQueue中:java.lang.ref.WeakReference@233c0b17
appleReferenceQueue中:java.lang.ref.WeakReference@61443d8f
appleReferenceQueue中:java.lang.ref.WeakReference@445b84c0
appleReferenceQueue中:java.lang.ref.WeakReference@61a52fbd

总结

一个对象被某个Reference持有后,会被Garbage Collector 特殊对待。当这个对象被回收后,视情况对这个对象对应的Reference做处理:

  • 如果构建Reference时,没有传入ReferenceQueue类型参数,那么不做任何操作;
  • 如果传入了ReferenceQueue类型参数,那么把这个对象对应的Referenc加入到 pending-Reference链表,然后再把pending-Reference链表中元素加入到传入的ReferenceQueue中;

关于Reference的四种类型,使用实例,以及各自的用途,会在下一篇博客中讲到。

参考与感谢

关于Java中的WeakReference
阿里面试: 说说强引用、软引用、弱引用、虚引用吧
一提到Reference 百分之九十九的java程序员都懵逼了

猜你喜欢

转载自blog.csdn.net/qq_43579103/article/details/108840706