JDK源码解析/深入理解Reference和ReferenceQueue

一、Reference简介

1.reference状态

Active:新创建的实例状态,当被垃圾收集器回收时,如果实例注册时候指定了队列,则变成Pending状态,否则变成Inactive状态。
Pending:pending-Reference列表的一个元素,等待被Reference-handler线程入队。解注册的实例不会是这种状态。
Enqueued:实例创建的时候指定的queue的一个元素,当实例从queue移除的时候,状态变成Inactive。解注册的实例不会是这种状态。
Inactive: 当一个实例的状态变成Inactive,它的状态永远不会改变。

2. 状态改变

如果初始化一个引用传入了ReferenceQueue,reference状态变化过程:Active->Pending->Enqueued->Inactive。
如果没有传入ReferenceQueue,Active->Inactive。没有入队的过程。

3.简单举例 

以弱引用举例,垃圾收集器工作,回收弱引用指向的对象(referent,referent状态变成finalizable)那一部分内存,由Reference-handler线程把pending状态的引用(reference)放入queue里面,然后把pending的discovered变成下一个pending。Reference-handler线程是一个高优先级的线程,并非是在垃圾回收器工作之后才去工作,而是一直等待着pending的值,有值就入队。

以下为猜想,帮助更好理解:如果有四个弱引用r1、r2、r3、r4,没有被回收,状态都为Active,r1->discovered=r2,r2->discovered=r3,r3->discovered=r4,进行GC,Reference的静态变量pending值被GC赋值为r1,r1的状态变成Pending,Reference-handler线程检测到pending被赋值,把r1放入queue,r1状态变成Enqueued,然后把pending的值改为r1的成员变量discovered的值,即r2,r2的状态被变成Pending,重复这个过程,r1、r2、r3、r4依次进入queue,状态都变成Enqueued,而r4的discovered为空,pending又为空,等待GC赋值。

二、ReferenceQueue简介

1.ReferenceQueue名字叫队列,实际是个栈,按照后入先出的方式处理引用,后面还是称呼队。
2.入队之后把引用内部的queue置为ENQUEUED,也就是一个空队列。如果引用的queue为空或者ENQUEUED则不处理。然后把当前引用的next指向之前的head,head指向当前引用。
3.出队就是把head弹出。出队有两种方法,一种head为空,一直等待。另一种是head为空,直接返回。
ReferenceQueue主要是用来追踪被GC掉的引用。比如WeakHashMap,把ReferenceQueue里面的引用key从Map里面删除。
如果引用从queue里面remove了,则状态变成Inactive。

三、Reference源码

public abstract class Reference<T> {

    /* A Reference instance is in one of four possible internal states:
     *
     *     Active: Subject to special treatment by the garbage collector.  Some
     *     time after the collector detects that the reachability of the
     *     referent has changed to the appropriate state, it changes the
     *     instance's state to either Pending or Inactive, depending upon
     *     whether or not the instance was registered with a queue when it was
     *     created.  In the former case it also adds the instance to the
     *     pending-Reference list.  Newly-created instances are Active.
     *
     *     Pending: An element of the pending-Reference list, waiting to be
     *     enqueued by the Reference-handler thread.  Unregistered instances
     *     are never in this state.
     *
     *     Enqueued: An element of the queue with which the instance was
     *     registered when it was created.  When an instance is removed from
     *     its ReferenceQueue, it is made Inactive.  Unregistered instances are
     *     never in this state.
     *
     *     Inactive: Nothing more to do.  Once an instance becomes Inactive its
     *     state will never change again.
     *
     * The state is encoded in the queue and next fields as follows:
     *
     *     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.
     *
     * With this scheme the collector need only examine the next field in order
     * to determine whether a Reference instance requires special treatment: If
     * the next field is null then the instance is active; if it is non-null,
     * then the collector should treat the instance normally.
     *
     * To ensure that a concurrent collector can discover active Reference
     * objects without interfering with application threads that may apply
     * the enqueue() method to those objects, collectors should link
     * discovered objects through the discovered field. The discovered
     * field is also used for linking Reference objects in the pending list.
     */

    private T referent;   // 引用的具体对象,被垃圾收集器回收,值为null      /* Treated specially by GC */

    volatile ReferenceQueue<? super T> queue; // 初始化的时候被设置

    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    Reference next;

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
// discovered:下一个被pending的引用。
// 如果当前引用是active状态,discovered值为GC保留的discovered引用列表的下一个元素。
// 如果当前引用是pending状态,discovered为pending列表的下一个元素,如果已经是最后一个了,值为null。
    transient private Reference<T> discovered;  /* used by VM */


    /* Object used to synchronize with the garbage collector.  The collector
     * must acquire this lock at the beginning of each collection cycle.  It is
     * therefore critical that any code holding this lock complete as quickly
     * as possible, allocate no new objects, and avoid calling user code.
     */
    static private class Lock { }
    private static Lock lock = new Lock(); // Reference-handler线程在pending没有值的时候会用这把锁去等待,GC给pending赋值之后会用这把锁唤醒Reference-handler线程


    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null; // 类变量,由GC控制

    /* High-priority thread to enqueue pending References
     */
    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(true); // 一直循环去处理pending入队
            }
        }
    }

    /**
     * Try handle pending {@link Reference} if there is one.<p>
     * Return {@code true} as a hint that there might be another
     * {@link Reference} pending or {@code false} when there are no more pending
     * {@link 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 Reference}, wait until notified from VM
     *                      or interrupted; if {@code false}, return immediately
     *                      when there is no pending {@link Reference}.
     * @return {@code true} if there was a {@link 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) {
        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; // 下一个pending的值就是当前pending的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(); // 如果pending没有值,则等待GC赋值
                    }
                    // 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); // 把引用放入引用的queue中
        return true;
    }

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

    /* -- 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; // 给代码显示调用的,留一个后门,让GC回收
    }


    /* -- 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;
    }

四、ReferenceQueue源码

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

    @SuppressWarnings("unchecked")
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head; // 头出队
        if (r != null) {
            head = (r.next == r) ?
                null :
                r.next; // Unchecked due to the next field having a raw type in Reference
            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);
    }

}

五、测试代码:

	private static final int _1M = 1024 * 1024;

	private static ReferenceQueue<LargeObject> refQueue = new ReferenceQueue<LargeObject>();

	public static void main(String[] args) {
		useMyQueueReference();
	}

	/**
	 * 使用自定义的引用类型
	 */
	private static void useMyQueueReference() {

		/* 分配1M,目前新生代空间使用1M */
		printPrepareMessage("strong1");
		LargeObject strong1 = new LargeObject(_1M, "strong1");

		/* 分配4M,目前新生代空间使用5M */
		List<MySoftReference<LargeObject>> softRefs = new ArrayList<MySoftReference<LargeObject>>();
		for (int i = 1; i <= 2; i++) {
			String name = "soft" + i;
			printPrepareMessage(name);
			softRefs.add(new MySoftReference<LargeObject>(name, new LargeObject(2 * _1M, name), refQueue));
		}

		/* 分配6M,目前新生代空间使用11M */
		List<MyWeakReference<LargeObject>> weakRefs = new ArrayList<MyWeakReference<LargeObject>>();
		for (int i = 1; i <= 2; i++) {
			String name = "weak" + i;
			printPrepareMessage(name);
			weakRefs.add(new MyWeakReference<LargeObject>(name, new LargeObject(3 * _1M, name), refQueue));
		}

		checkSoftList(softRefs); // 引用的对象还在
		checkWeakList(weakRefs); // 引用的对象还在

		System.gc();
		System.out.println("=====After GC.");

		checkQueue(); // 不一定会立刻入队,因为在pending列里面等待入队,弱引用的对象为null

		/* 分配15M,导致内存不足前回收 */
		printPrepareMessage("strong2");
		LargeObject strong2 = new LargeObject(15 * _1M, "strong2");
		checkQueue(); // // 不一定会立刻入队,因为在pending列里面等待入队,弱引用的对象为null
	}

	private static void checkWeakList(List<MyWeakReference<LargeObject>> weakRefs) {
		for (MyWeakReference<LargeObject> weakRef : weakRefs) {
			System.out.println("=In weak list : " + weakRef.get());
		}
	}

	private static void checkQueue() {
		Reference<? extends LargeObject> inq = null;
		while ((inq = refQueue.poll()) != null) {
			if (inq instanceof MySoftReference) {
				System.out.println("=Enqueued:" + ((MySoftReference) inq).getName() + " object:" + inq.get());
			} else if (inq instanceof MyWeakReference) {
				System.out.println("=Enqueuede:" + ((MyWeakReference) inq).getName() + " object:" + inq.get());
			} else {
				System.out.println("=Enqueued:" + inq.getClass().getSimpleName() + " object:" + inq.get());
			}
		}
	}

	private static void printPrepareMessage(String name) {
		System.out.println("Prepare to create " + name);
	}

	static class LargeObject {
		private byte[] data;
		private String name;

		public LargeObject(int size, String name) {
			data = new byte[size];
			this.name = name;
			System.out.println("Over Constructing LargeObject " + name + System.lineSeparator());
		}

		public String getName() {
			return name;
		}
	}
public class MySoftReference<T> extends SoftReference<T> {

	private String name;

	public MySoftReference(String name, T referent) {
		super(referent);
		this.name = name;
	}

	public MySoftReference(String name, T referent, ReferenceQueue<? super T> q) {
		super(referent, q);
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

public class MyWeakReference<T> extends WeakReference<T> {

	private String name;

	public MyWeakReference(String name, T referent) {
		super(referent);
		this.name = name;
	}

	public MyWeakReference(String name, T referent, ReferenceQueue<? super T> q) {
		super(referent, q);
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

六、测试环境

JDK1.8.0_144,Java HotSpot(TM) 64-Bit Server VM 
-Xms40m -Xmx40m -Xmn20m -XX:+PrintGCDetails

七、测试结果及分析

Prepare to create strong1
Over Constructing LargeObject strong1

Prepare to create soft1
Over Constructing LargeObject soft1

Prepare to create soft2
Over Constructing LargeObject soft2

Prepare to create weak1
Over Constructing LargeObject weak1

Prepare to create weak2
Over Constructing LargeObject weak2

// 在软引用队列和弱引用队列里面,引用的对象都存在
=In soft list : com.test.reference.TestReference$LargeObject@7852e922
=In soft list : com.test.reference.TestReference$LargeObject@4e25154f
=In weak list : com.test.reference.TestReference$LargeObject@70dea4e
=In weak list : com.test.reference.TestReference$LargeObject@5c647e05

// 调用System.gc()显示GC,回收了弱引用指向的对象
[GC (System.gc()) [PSYoungGen: 12493K->1736K(17920K)] 12493K->5840K(38400K), 0.0031694 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 1736K->0K(17920K)] [ParOldGen: 4104K->5676K(20480K)] 5840K->5676K(38400K), [Metaspace: 2792K->2792K(1056768K)], 0.0062129 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
=====After GC.

// 从Reference Queue可以看到弱引用入队,状态变成了Enqueued,引用所指的对象为null
=Enqueuede:weak2 object:null
=Enqueuede:weak1 object:null
Prepare to create strong2
[GC (Allocation Failure) [PSYoungGen: 299K->96K(17920K)] 5976K->5772K(38400K), 0.0002436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 96K->64K(17920K)] 5772K->5740K(38400K), 0.0001579 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(17920K)] [ParOldGen: 5676K->5675K(20480K)] 5740K->5675K(38400K), [Metaspace: 2793K->2793K(1056768K)], 0.0048514 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(17920K)] 5675K->5675K(38400K), 0.0001766 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

// 多次GC无果,即将OOM,所以回收了软引用指向的对象
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(17920K)] [ParOldGen: 5675K->1566K(20480K)] 5675K->1566K(38400K), [Metaspace: 2793K->2793K(1056768K)], 0.0048725 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
Over Constructing LargeObject strong2

// 从Reference Queue可以看到软引用入队,状态变成了Enqueued,引用所指的对象为null
=Enqueued:soft2 object:null
=Enqueued:soft1 object:null
Heap
 PSYoungGen      total 17920K, used 607K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 15360K, 3% used [0x00000000fec00000,0x00000000fec97ce0,0x00000000ffb00000)
  from space 2560K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x0000000100000000)
  to   space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000)
 ParOldGen       total 20480K, used 16926K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  object space 20480K, 82% used [0x00000000fd800000,0x00000000fe887ac8,0x00000000fec00000)
 Metaspace       used 2800K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 303K, capacity 388K, committed 512K, reserved 1048576K

猜你喜欢

转载自blog.csdn.net/shi2huang/article/details/80112960