Java中的引用Reference下篇(实践)

一、Reference类解析

(一)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 的构造函数


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

通过比较三种引用方式的源码,都是在不同程度上重写父类Reference的方法。

WeakReference 和 SoftReference 都有不设置 ReferenceQueue 对象构造方法,但是 PhantomReference 只有包含 ReferenceQueue 对象的构造方法,说明创建 PhantomReference 对象,必须设置对应的 ReferenceQueue 对象。

PhantomReference 的 get 方法直接返回 null,说明它获取不到所对应的引用对象。

3.1相关代码
// 软引用
public class SoftReference<T> extends Reference<T> {
    // 时间戳锁,由垃圾收集器更新
    static private long clock;
    // 每次调用get方法时更新的时间戳。虚拟机在选择需要清除的软引用时,可能会使用该字段,但不需要。从下面的get方法有对该时间戳的更新。
    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;
    }
}

// 弱引用
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 PhantomReference<T> extends Reference<T> {
    //  因为虚引用的引用总是不可访问的,所以这个方法总是返回null
    public T get() {
        return null;
    }
    //创建一个新的虚引用,该幻像引用给定的对象并在给定的队列中注册。
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

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 对象。起到监控作用。

参考地址:https://www.jianshu.com/p/ac081b13f8fb

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值