美文网首页程序员
java引用Reference详细分析

java引用Reference详细分析

作者: wo883721 | 来源:发表于2020-04-24 19:04 被阅读0次

    众所周知,java 中引用分为四种类型,分别为强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)。四种引用的强度顺序是:

    StrongReference > SoftReference > WeakReference > PhantomReference
    

    其中 StrongReference 没有具体具现化的类, Object obj = new Object() 这个就属于强引用。
    想弄懂这四种引用类型,就必须先了解 ReferenceReferenceQueue 这两个类。

    一. 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 实例,它有四种状态:

    1. Active 状态:表示这个 Reference 是新创建状态,且它对应的引用对象 referent 也没有被 gc 回收。
    2. Pending 状态:表示这个 Reference 所对应的引用对象 referent 已经被 gc 回收了,等待将这个 Reference 对象放到 queue 队列中。

    因此如果这个 Reference 对象创建的时候,没有设置 queue 对象,那么就不会进入这个状态。

    1. Enqueued 状态:表示这个 Reference 对象已经被放到 queue 队列中。

    Pending 状态一样,如果创建 Reference 对象时,没有设置 queue 对象,那么也不会进入这个状态。

    1. Inactive 状态:表示这个 Reference 对象最终状态,无法再改变。

    如果这个 Reference 对象没有设置 queue 对象,那么当这个 Reference 所对应的引用对象 referentgc 回收后,就变成这个状态了。如果设置了 queue 对象,就必须等到有人将这个 Reference 对象从 queue 队列中取出,才会进入这个状态。

    所以我们可以看出,根据有没有设置 queue 对象,Reference 状态变化分为两种:

    1. 没有设置 queue 对象。当 Reference 对象创建成功后,就是 Active 状态,等到 Reference 所对应的引用对象 referentgc 回收了,那么就进入了 Inactive 状态。
    2. 设置 queue 对象。当 Reference 对象创建成功后,就是 Active 状态,等到 Reference 所对应的引用对象 referentgc 回收了,就进入了 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 四种状态的意义,那么就知道它的成员属性的作用了:

    1. referent : Reference 实例所对应的引用对象。
    2. queue : 用来储存 Reference 实例的队列。
    3. next : 通过这个属性形成一个队列,用来记录这个 Reference 对象的 queue 对象中所有 Enqueued 状态的对象。

    active: NULL
    pending: this
    Enqueued: next reference in queue (or this if last)
    Inactive: this

    1. discovered : 通过这个属性形成一个队列,用来记录所有处于 Pending 状态的 Reference 对象。
    2. 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;
    

    属性分析:

    1. Null 内部类:enqueue 方法返回 false,表示这个 ReferenceQueue 队列不能存放 Reference 对象。
    2. NULLENQUEUED : 用于特殊标记。
    3. head :表示这个 ReferenceQueue 队列的队列头。
    4. 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 对象的 queuenext 属性。

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

    观察一下这三个类,我们发现:

    1. WeakReferenceSoftReference 都有不设置 ReferenceQueue 对象构造方法,但是 PhantomReference 只有包含 ReferenceQueue 对象的构造方法,说明创建 PhantomReference 对象,必须设置对应的 ReferenceQueue 对象。
    2. PhantomReferenceget 方法直接返回 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 的主要作用有以下几点:

    1. 使用 Reference 包装一个引用对象 referent,如果这个引用对象 referentgc 回收了,那么 Reference 对象的 get 方法获取的引用对象 referent 就为 null 。这一步是由 gc 线程设置的。

    注意,根据 Reference 类型不同, gc 线程何时回收 Reference 所包装一个引用对象 referent 也不同。PhantomReference 对象的 get 方法返回一直为空。

    1. 如果给 Reference 设置一个 ReferenceQueue 队列,那么当 Reference 包装一个引用对象 referentgc 回收时,可以从 ReferenceQueue 队列中获取到所有 referent 被回收的 Reference 对象。起到监控作用。

    相关文章

      网友评论

        本文标题:java引用Reference详细分析

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