众所周知,java 中引用分为四种类型,分别为强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)。四种引用的强度顺序是:
StrongReference > SoftReference > WeakReference > PhantomReference
其中 StrongReference 没有具体具现化的类, Object obj = new Object() 这个就属于强引用。
想弄懂这四种引用类型,就必须先了解 Reference 和 ReferenceQueue 这两个类。
一. Reference 类
1.1 重要属性
private T referent; 
volatile ReferenceQueue<? super T> queue;
volatile Reference next;
transient private Reference<T> discovered;
private static Reference<Object> pending = null;
1.1.1 四种状态
想要了解这些属性的作用,先要知道对于一个 Reference 实例,它有四种状态:
- 
Active状态:表示这个Reference是新创建状态,且它对应的引用对象referent也没有被gc回收。 - 
Pending状态:表示这个Reference所对应的引用对象referent已经被gc回收了,等待将这个Reference对象放到queue队列中。 
因此如果这个
Reference对象创建的时候,没有设置queue对象,那么就不会进入这个状态。
- 
Enqueued状态:表示这个Reference对象已经被放到queue队列中。 
和
Pending状态一样,如果创建Reference对象时,没有设置queue对象,那么也不会进入这个状态。
- 
Inactive状态:表示这个Reference对象最终状态,无法再改变。 
如果这个
Reference对象没有设置queue对象,那么当这个Reference所对应的引用对象referent被gc回收后,就变成这个状态了。如果设置了queue对象,就必须等到有人将这个Reference对象从queue队列中取出,才会进入这个状态。
所以我们可以看出,根据有没有设置 queue 对象,Reference 状态变化分为两种:
- 没有设置 
queue对象。当Reference对象创建成功后,就是Active状态,等到Reference所对应的引用对象referent被gc回收了,那么就进入了Inactive状态。 - 设置  
queue对象。当Reference对象创建成功后,就是Active状态,等到Reference所对应的引用对象referent被gc回收了,就进入了Pending状态,然后等待ReferenceHandler线程将Reference状态变成Enqueued状态。最后等待用户将这个Reference对象从queue队列中取出,那么变成了Inactive状态。 
1.1.2 属性作用
先看一下 Reference 的构造函数
    Reference(T referent) {
        this(referent, null);
    }
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
当我们创建 Reference 时,没有设置 queue 对象,那么默认的 queue 就是 ReferenceQueue.NULL。
当我们了解了 Reference 四种状态的意义,那么就知道它的成员属性的作用了:
- 
referent:Reference实例所对应的引用对象。 - 
queue: 用来储存Reference实例的队列。 - 
next: 通过这个属性形成一个队列,用来记录这个Reference对象的queue对象中所有Enqueued状态的对象。 
active: NULL
pending: this
Enqueued: next reference in queue (or this if last)
Inactive: this
- 
discovered: 通过这个属性形成一个队列,用来记录所有处于Pending状态的Reference对象。 - 
pending: 这个是一个静态属性,所有Reference对象共享的。 
1.2 ReferenceHandler 内部类
1.2.1 ReferenceHandler 类
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 {
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }
这个类的实现很简单,就不多介绍了。
1.2.2 静态代码块调用
public abstract class Reference<T> {
....
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new Reference.ReferenceHandler(tg, "Reference Handler");
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
}
在 Reference 的静态代码块中,开启 ReferenceHandler 线程,名字叫 Reference Handler 。
所以当你用
jstack pid查看java的线程栈,你会看到一个名叫Reference Handler线程.
1.2.3 tryHandlePending 方法
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            // 防止并发冲突
            synchronized (lock) {
                // 如果静态变量有值,pending就是一个 pending状态的 Reference 对象,需要将它变成 `Enqueued` 状态
                if (pending != null) {
                    r = pending;
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // 设置下一个 `pending` 状态的  Reference 对象
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    if (waitForNotify) {
                        // 如果没有,就等待,直到 gc 进行动作
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
        if (c != null) {
            c.clean();
            return true;
        }
        
        ReferenceQueue<? super Object> q = r.queue;
        // 将 Reference 对象放入自己的 queue 队列中,变成 `Enqueued` 状态
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }
通过上面代码,我们直到 ReferenceHandler 类就是一个线程,它的作用就是将所有 Pending 状态的 Reference 对象,放入自己的 queue 队列中,变成 Enqueued 状态。
将
Reference对象从Active状态变成Pending状态,是由gc帮我们做的,这块代码我没有找到。
为什么需要  ReferenceHandler 这个线程,来将 Reference 对象从 Pending 状态变成 Enqueued 状态,而不是有 gc 程序一起做了呢?
我想可能是不想影响 gc 程序的效率,所以另开一个线程,来异步处理这些事情。
二. ReferenceQueue 类
2.1 重要属性
    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;
属性分析:
- 
Null内部类:enqueue方法返回false,表示这个ReferenceQueue队列不能存放Reference对象。 - 
NULL和ENQUEUED: 用于特殊标记。 - 
head:表示这个ReferenceQueue队列的队列头。 - 
queueLength:表示这个ReferenceQueue队列中存放的Reference对象数量。 
2.2 重要方法
2.2.1  enqueue 入队方法
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
            ReferenceQueue<?> queue = r.queue;
            // 如果 Reference对象的 queue 是 NULL 或者 ENQUEUED,
            // 表示这个 Reference对象不能存放到queue队列中
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            // 如果 queue 不是当前 ReferenceQueue 对象,直接报错
            assert queue == this;
            // 将 r.queue 变成 ENQUEUED,表示这个Reference对象状态是Enqueued
            r.queue = ENQUEUED;
            // 使用 next 来形成链条
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
主要修改了 Reference 对象的 queue 和 next 属性。
2.2.2  poll 出队方法
    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
    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 指向队列中的下一个 Reference 对象
            head = (rn == r) ? null : rn;
            // 将 queue 设置成 null
            r.queue = NULL;
            // 将 next 设置成 自己
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }
从 ReferenceQueue 队列中获取一个 Reference 对象,这个 Reference 对象状态从 Enqueued 变成 Inactive 。
如果当前
ReferenceQueue队列没有数据,那么返回null。
2.2.2  remove 等待删除方法
    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    }
    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;
                }
            }
        }
    }
这个方法与 poll 方法比较,它们都能移除 ReferenceQueue 中的一个 Reference 对象,并且改变这个 Reference 对象状态。
当时  poll 方法的缺陷是你不知道 Reference 对象什么时候被加入到 ReferenceQueue 队列中。  而 remove 方法可以一直等待到 ReferenceQueue 队列中有数据。
三. PhantomReference、WeakReference 和 SoftReference
3.1 相关代码
    public class PhantomReference<T> extends Reference<T> {
       // 获取都是 0
        public T get() {
            return null;
        }
        public PhantomReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    
    public class WeakReference<T> extends Reference<T> {
        public WeakReference(T referent) {
            super(referent);
        }
        public WeakReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    public class SoftReference<T> extends Reference<T> {
        static private long clock;
        private long timestamp;
        public SoftReference(T referent) {
            super(referent);
            this.timestamp = clock;
        }
        public SoftReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
            this.timestamp = clock;
        }
        public T get() {
            T o = super.get();
            if (o != null && this.timestamp != clock)
                this.timestamp = clock;
            return o;
        }
    }
观察一下这三个类,我们发现:
- 
WeakReference和SoftReference都有不设置ReferenceQueue对象构造方法,但是PhantomReference只有包含ReferenceQueue对象的构造方法,说明创建PhantomReference对象,必须设置对应的ReferenceQueue对象。 - 
PhantomReference的get方法直接返回null,说明它获取不到所对应的引用对象。 
3.2 示例代码
   public static class User {
        private String name;
        public User(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("User{");
            sb.append("name='").append(name).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }
    public static void main(String[] agrs) throws InterruptedException {
        User user = new User("zhang");
        ReferenceQueue<User> queue = new ReferenceQueue<>();
        Reference<User> reference = new PhantomReference<>(user, queue);
        System.out.println("reference:  "+ reference);
        
        // 将引用对象设置成 null,可以让 gc 程序进行回收
        user = null;
        System.out.println("reference.get():  "+ reference.get());
        System.out.println("reference.isEnqueued():  "+reference.isEnqueued());
        System.out.println("queue.poll():  "+queue.poll());
        // 进行 gc
        System.gc();
        System.out.println("===============gc 之后=================");
        System.out.println("reference.get():  "+ reference.get());
        System.out.println("reference.isEnqueued():  "+reference.isEnqueued());
        System.out.println("queue.poll():  "+queue.poll());
    }
运行结果:
reference:  java.lang.ref.PhantomReference@60e53b93
reference.get():  null
reference.isEnqueued():  false
queue.poll():  null
===============gc 之后=================
reference.get():  null
reference.isEnqueued():  true
queue.poll():  java.lang.ref.PhantomReference@60e53b93
将 PhantomReference 换成 WeakReference,运行结果:
reference:  java.lang.ref.WeakReference@60e53b93
reference.get():  User{name='zhang'}
reference.isEnqueued():  false
queue.poll():  null
===============gc 之后=================
reference.get():  null
reference.isEnqueued():  true
queue.poll():  java.lang.ref.WeakReference@60e53b93
将 PhantomReference 换成 SoftReference,运行结果:
reference:  java.lang.ref.SoftReference@60e53b93
reference.get():  User{name='zhang'}
reference.isEnqueued():  false
queue.poll():  null
===============gc 之后=================
reference.get():  User{name='zhang'}
reference.isEnqueued():  false
queue.poll():  null
可以看出,对于 SoftReference 所引用的对象,即使调用 System.gc() 之后,不会将这个引用的对象回收。
四. 总结
从源码中我们可以得出,Reference 的主要作用有以下几点:
- 使用 
Reference包装一个引用对象referent,如果这个引用对象referent被gc回收了,那么Reference对象的get方法获取的引用对象referent就为null。这一步是由gc线程设置的。 
注意,根据
Reference类型不同,gc线程何时回收Reference所包装一个引用对象referent也不同。PhantomReference对象的get方法返回一直为空。
- 如果给 
Reference设置一个ReferenceQueue队列,那么当Reference包装一个引用对象referent被gc回收时,可以从ReferenceQueue队列中获取到所有referent被回收的Reference对象。起到监控作用。