【JAVA Reference】ReferenceQueue 与 Reference 源码剖析(二)

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码


一、架构

1.1 代码架构图

在这里插入图片描述

1.2 状态流程图
1.2.1 简易流程图

在这里插入图片描述
在这里插入图片描述

1.2.2 UML流程图 (单ReferenceQueue)

在这里插入图片描述

1.2.3 UML流程图 (多ReferenceQueue)

在这里插入图片描述

1.2.4 Reference对象的状态通过成员变量next和queue来判断
状态NextQueuediscovered
ActivenullreferenceQueue下一个要回收的 reference(gc决定)(假如是最后一个,那么就是 this )
PendingthisreferenceQueue下一个要进入队列的 reference (假如是最后一个,那么就是 this )
Enqueued队列中下一个节点(假如是最后一个,next = this)ReferenceQueue.ENQUEUEDnull
InactivethisReferenceQueue.NULLnull

在 Active 与 Pending 状态下的, discovered 就相当于 next。

1.2.5 未注册 ReferenceQueue

如果未注册 Queue,那么就只有 Active 与 Inactive 状态。
在这里插入图片描述

=== 点击查看top目录 ===

二、ReferenceQueue 源码剖析

ReferenceQueue队列是一个单向链表,ReferenceQueue里面只有一个head 成员变量持有队列的队头。后进先出的队列,其实是个就是个栈!!!

2.1 ReferenceQueue 类

ReferenceQueue和 Reference 类都是 jdk1.2 的时候出的,所以也就不可能继承jdk1.5出来的Queue接口

package java.lang.ref;
import java.util.function.Consumer;
public class ReferenceQueue<T> {

    public ReferenceQueue() { }

    private static class Null<S> extends ReferenceQueue<S> {
        boolean enqueue(Reference<? extends S> r) {
            return false;  //内部类,它是用来做状态识别的,重写了enqueue入队方法,永远返回false,所以它不会存储任何数据,见后面的NULL和ENQUEUED两个标识成员变量
        }
    }

    /*
    	状态State:Inactive
    	1. 当 Reference对象创建时没有指定 queue,设置为 NULL
    	2. 该 Reference 被拉出队列的时候,设置为 NULL
	*/ 
    static ReferenceQueue<Object> NULL = new Null<>();

    /*
    	状态State:ENQUEUED
    	Reference已经被ReferenceHander线程从pending队列移到queue里面时。设置为 ENQUEUED
    	所以ENQUEUED状态不可能在调用 enqueue 方法
    */
    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;
	// 如果Reference创建时没有指定队列或Reference对象已经在队列里面了,则直接返回
    boolean enqueue(Reference<? extends T> r) { }

    private Reference<? extends T> reallyPoll() {}
    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
 	//将头部第一个对象移出队列并返回,如果队列为空,则等待timeout时间后,返回null,这个方法会阻塞线程
    public Reference<? extends T> remove(long timeout){}
    //将队列头部第一个对象从队列中移除出来,如果队列为空则直接返回null(此方法不会被阻塞)
    public Reference<? extends T> remove(){return remove(0);}
    void forEach(Consumer<? super Reference<? extends T>> action) {}
}
2.1.1 Null 内部类
  • java.lang.ref.ReferenceQueue.Null
  • 内部类,它是用来做状态识别的,重写了enqueue入队方法,永远返回false,所以它不会存储任何数据,见后面的NULL和ENQUEUED两个标识成员变量
private static class Null<S> extends ReferenceQueue<S> {
        boolean enqueue(Reference<? extends S> r) {
            return false; 
        }
    }

=== 点击查看top目录 ===

2.2 enqueue 方法
   boolean enqueue(Reference<? extends T> r) {
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            ReferenceQueue<?> queue = r.queue;
            // 如果Reference创建时没有指定队列或Reference对象已经在队列里面了,则直接返回
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;   //!!! 只有r的队列是当前队列才允许入队
            r.queue = ENQUEUED; //将r的queue设置为ENQUEUED状态,标识Reference已经入队
            r.next = (head == null) ? r : head;  // 往头部插 ,类似于栈结构
            head = r; 
            queueLength++;
            if (r instanceof FinalReference) {
             	//如果r是一个FinalReference实例,那么将FinalReference数量也+1
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }

=== 点击查看top目录 ===

2.3 poll 方法
    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
  • reallyPoll 方法
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head; // 弹出 head ,从头部取,类似于栈结构,后进先出
        if (r != null) {
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            head = (rn == r) ? null : rn;
            r.queue = NULL; // 打个出队列标志 NULL 
            r.next = r; 
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

=== 点击查看top目录 ===

2.4 remove 方法
  • remove(long timeout)
 // 删掉头节点,队列是空的话,等待N秒
    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;
                }
            }
        }
    }
  • remove()
    // 删掉头节点,队列是空的话,一直等待下去, 等待notify
    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    }

=== 点击查看top目录 ===

2.5 forEach() 方法

这是jdk1.8 新增的接口,传入一个 Consumer,调用 foreach 消费队列中全部的Reference。

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

=== 点击查看top目录 ===

三、Reference 源码剖析

3.1 Reference 类

这是 referece对象的抽象base类,这个类定义了所有 reference 对象的通用方法,因为 reference 对象跟 GC垃圾。

/**
 * Abstract base class for reference objects.  This class defines the
 * operations common to all reference objects.  Because reference objects are
 * implemented in close cooperation with the garbage collector, this class may
 * not be subclassed directly.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */
// 这是 referece对象的抽象base类,这个类定义了所有 reference 对象的通用方法,因为 reference 对象跟 GC垃圾收集器密切合作。
public abstract class Reference<T> {
    private T referent;         /* Treated specially by GC */ 

    @SuppressWarnings("rawtypes")
    volatile Reference next;

    static private class Lock { }
    //lock成员变量是pending队列的全局锁,
    //1. tryHandlePending 加锁出队列
    //2. jvm垃圾回收器线程入pending队列,往pending里面添加Reference对象
    private static Lock lock = new Lock();

    //pending队列, pending成员变量与后面的discovered对象一起构成了一个pending单向链表,注意这个成员变量是一个静态对象,所以是全局唯一的,pending为链表的头节点,discovered为链表当前Reference节点指向下一个节点的引用
    // 这个队列是由jvm的垃圾回收器构建的,当对象除了被reference引用之外没有其它强引用了,jvm的垃圾回收器就会将指向需要回收的对象的Reference都放入到这个队列里面
    private static Reference<Object> pending = null;

    //与成员变量pending一起组成pending队列,指向链表当前节点的下一个节点
    transient private Reference<T> discovered;  /* used by VM */

    //ReferenceQueue队列,ReferenceQueue并不是一个链表数据结构,它只持有这个链表的表头对象header,这个链表是由Refence对象里面的next成员变量构建起来的,next也就是链表当前节点的下一个节点(只有next的引用,它是单向链表)
    volatile ReferenceQueue<? super T> queue;

    //以下是ReferenceHander线程初始化并启动的操作
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler"); //线程名称为Reference Handler
        handler.setPriority(Thread.MAX_PRIORITY); //线程有最高优先级
        handler.setDaemon(true);//设置线程为守护线程;
        handler.start();
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
    Reference(T referent) {this(referent, null);}

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

    static boolean tryHandlePending(boolean waitForNotify) {
        //从pending中移除的Reference对象
        Reference<Object> r;
        Cleaner c;
        try {
             //此处需要加全局锁,因为除了当前线程,gc线程也会操作pending队列。GC 往pending队列塞Reference,该方法是取出来
            synchronized (lock) {
                 //如果pending队列不为空,则将第一个Reference对象取出
                if (pending != null) {
                    r = pending;  //缓存pending队列头节点
                    c = r instanceof Cleaner ? (Cleaner) r : null; 
                    pending = r.discovered; //将头节点指向discovered,discovered为pending队列中当前节点的下一个节点,这样就把第一个头结点出队了
                    r.discovered = null;  //将当前节点的discovered设置为null;当前节点出队,不需要组成链表了;
                } else {
                    //如果pending队列为空
                    if (waitForNotify) { //若要等待被唤醒,那么 wait,
                        lock.wait();
                    }
                    return waitForNotify; //直接返回
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
        if (c != null) {
            // 如果从pending队列出队的r是一个Cleaner对象,那么直接执行其clean()方法执行清理操作;
            c.clean();

             //注意这里直接 return true返回,这里已经不往下执行了,所以Cleaner对象是不会进入到队列里面的,给它设置ReferenceQueue的作用是为了让它能进入Pending队列后被ReferenceHander线程处理;
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
         //将对象放入到它自己的ReferenceQueue队列里
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }


    public T get() {return this.referent;}
    public void clear() {this.referent = null;}
    public boolean isEnqueued() {return (this.queue == ReferenceQueue.ENQUEUED);}
    public boolean enqueue() {return this.queue.enqueue(this);}

    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);
        }
        //从源码中可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级和daemon状态。此线程要做的事情就是不断的的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进行wait状态。
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }
}

从注释可以知道,该 Reference 类跟 GC回收密切相关。
=== 点击查看top目录 ===

3.2 Reference 的四种状态

instance 实例对象就是一个 reference 对象

3.2.1 状态Active
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.

该状态下的 Reference 会收到 GC 收集器的特别对待。有时候,当 GC 收集器检测到 referent 的可达性变更为其他合适状态的时候。它会把 instance 的状态改变为 Pending 或者 Inactive,这取决于你创建 instance 的时候,
有没有注册 ReferenceQueue,如果有(前者),那么会将这个 instance 加到 pending-Reference 链表里面去。(状态改成 Pending)后者的话,就是没注册 ReferenceQueue 的话,那么状态变成(Inactive)
新创建的对象实例状态都是 Active
3.2.2 状态Pending
Pending: An element of the pending-Reference list, waiting to be
enqueued by the Reference-handler thread. Unregistered instances
are never in this state.

一个 pending-Reference list中的元素,等待 Reference-handler线程 安排进队列之前就是这个 Pending 状态。没有注册的 instance 永远不可能到达这一状态。

3.2.3 状态Enqueued
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.

当创建 Reference 的时候有注册 queue 的元素。当一个 instance 从 ReferenceQueue 中 remove 的时候,
Reference 状态变成 Inactive。没有注册ReferenceQueue的不可能到达这一状态的。

3.2.4 状态Inactive
Inactive: Nothing more to do. Once an instance becomes Inactive its
state will never change again.

没啥事可做。一旦一个 instance 变成 Inactive 状态,那么不会再改变了。
3.2.5 状态总结
  1. 新创建的Reference实例就是Active的
  2. 进入pending队列就是就是pending状态
  3. 通过Reference-Handler线程放入queue队列中,那么就是Enqueued状态
  4. 当对象出队之后,那么就会变为inactive状态
3.2.6 流程总结
  1. New 新创建对象。
  2. 如若GC收集器觉得应该垃圾回收,那么放进 pending 队列里面。
    pending队列, pending成员变量与后面的discovered对象一起构成了一个pending单向链表,(这个变量是一个静态对象,所以是全局唯一的pending为链表的头节点,discovered为链表当前Reference节点指向下一个节点的引用),这个队列是由jvm的垃圾回收器构建的,当对象除了被reference引用之外没有其它强引用了,jvm的垃圾回收器就会将指向需要回收的对象的Reference都放入到这个队列里面(好好理解一下这句话,注意是指向要回收的对象的Reference,要回收的对象就是Reference的成员变量refernt持有的对象,是referent 持有的对象要被回收,而不是Reference对象本身)。
  3. 从 pending 队列进入ReferenceQueue(初始化的时候假如没有指定 ReferenceQueue,直接进入步骤4)
    这个pending 队列会由ReferenceHander线程来处理(ReferenceHander线程是jvm的一个内部线程,它也是Reference的一个内部类,它的任务就是将pending队列中要被回收的Reference对象移除出来,如果Reference对象在初始化的时候传入了ReferenceQueue队列,那么就把从pending队列里面移除的Reference放到它自己的ReferenceQueue队列里。你可以操作这个队列。除此之外ReferenceHander线程还会做一些其它操作
  4. 被回收,释放内存空间。

=== 具体看状态流程图 ===

=== 点击查看top目录 ===

3.3 内部类ReferenceHandler
  • 内部类 java.lang.ref.Reference.ReferenceHandler
  • 该类继承了 Thread类,为毛不实现 Runnable ,我再想下?
  • 该类最主要的方法就是 Run 方法内部的 tryHandlePending 方法
private static class ReferenceHandler extends Thread {

    //确保类已经加载到 JVM 里面去了
    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 {
        // 预先加载 InterruptedException 与 Cleaner 类,避免懒加载的时候,外面已经出现了内存问题。
        ensureClassInitialized(InterruptedException.class);
        ensureClassInitialized(Cleaner.class);
    }

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

    public void run() {
        while (true) { // 死循环
            tryHandlePending(true);
        }
    }
}

=== 点击查看top目录 ===

3.4 Reference#tryHandlePending() 方法
  • 该方法所做的,就是不断轮训遍历 pending 队列,然后丢入到 referenceQueue 队列里面去。
static boolean Reference#tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        // 加一个锁,因为除了这里在操作出队列, GC也在网pending队列里面塞数据
        synchronized (lock) {
            // 如果 pending 队列不为空
            if (pending != null) {
                r = pending;
                // 对 Cleaner 特殊处理
                c = r instanceof Cleaner ? (Cleaner) r : null;
                
                pending = r.discovered; // 指向pending的下一个 Reference
                r.discovered = null; // 置空
            } else {
                // 如果需要等待唤醒,那么就进去睡觉
                if (waitForNotify) {
                    lock.wait();
                }
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) { // 这里捕获了OOM,表示还先别终止 JVM,再给他一次机会 
        // 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...
        // 给其他线程一些cpu时间使得他们能够丢掉一些活着的 reference 和让cpu回收点空间。
        // 并且防止在继承 Cleaner的时候 cpu 内部自旋
        Thread.yield();
        // retry
        return true;
    } catch (InterruptedException x) { // 就算被打断又怎样
        // retry
        return true;
    }

    // Fast path for cleaners
    // 如果是 cleaner 的话,那么不进入队列,直接调用 clean方法,然后回收
    if (c != null) {
        c.clean();
        return true;
    }

    ReferenceQueue<? super Object> q = r.queue;

    if (q != ReferenceQueue.NULL) q.enqueue(r); // queue有指定的话,入队列
    return true;
}
  1. 看下里面的 OOM 错误处理,其实 try 内部的方法,并没有方法会抛出异常,但是这里捕获 OOM,是想针对整个JVM的。先yield 让出内存。
  2. 忽略线程的 InterruptedException。
  3. 为什么可以忽略 OOM 和 InterruptedException?为什么这么屌? 因为这个功能是给 GC 收集器使用的,肯定要保证一直活下去。
  • tryHandlePending 之后
    在这里插入图片描述

=== 点击查看top目录 ===

3.5 Reference的static代码块
//以下是ReferenceHander线程初始化并启动的操作
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler"); //线程名称为Reference Handler
        handler.setPriority(Thread.MAX_PRIORITY); //线程有最高优先级
        handler.setDaemon(true);//设置线程为守护线程;
        handler.start(); // 启动内部类 Handler
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

Reference 类的 static 块,handler.start(); 启动了一个幽灵线程,这个线程就是 ReferenceHandler 线程类 ,它所做的,就是不断轮训遍历 pending 队列,然后丢入到 referenceQueue 队列里面去,即使遇到 OOM 或者 InterruptedException, 全部忽略。

=== 点击查看top目录 ===

3.6 Reference 构造方法
    Reference(T referent) {
        this(referent, null);
    }

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

四、番外篇

下一章节:【JAVA Reference】Cleaner 源码剖析(三)
上一章节:【JAVA Reference】Java的强引用、软引用、弱引用和虚引用(一)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值