原来的标题是:"一提到Reference 99.99%的java程序员都懵逼了",为啥改成汉字了呢?吐槽一下,因为CSDN出bug了,如果你用了%做标题,你的文章就别想用它的编辑器修改了,它的js脚本写的不够健壮,报错了;
java.lang.ref.Reference
java程序员都知道如果一个对象没有任何引用了,那么这个对象在gc的时候就被回收了,大部分java程序员是基于我们常见的强引用去理解引用的,强引用这种方式虽然简单直接,但是对于一些特殊的java对象,如缓存数据在内存紧张时自动释放掉空间防止oom、直接内存对象回收之前需自动释放掉其占用的堆外内存,当socket对象被回收之前关闭连接,当文件流对象被回收之前自动关闭打开的文件等操作,强引用就无能为力了,为了实现这些特殊需求,java还引入了除了强引以外的引用类型来辅助对象回收操作 或 监控对象的生存周期。
先来看看引用类型的大家族:
在java.lang.ref包下除了ReferenceQueue类,Finalizer、FinalReference、PhantomReference、SoftReference、WeakReference都直接或间接继承了Reference类,想想为什么没有强引用StrongReference?
理解Reference的关键点在于:
1. 两个队列pending与ReferenceQueue的作用;
2. Reference对象4种状态的转换;
3. ReferenceHander线程的处理过程,它是如何将Pending与ReferenceQueue关联起来的;
4. Pending队列数据来源;
理解了以上4个问题,才算真不再对Reference懵逼了,下面就带着这四个问题去看下面的内容。
Reference构造方法
我们先来看看这些引用类型的父类都给我们提供了什么?
其内部提供2个构造函数,除了传入referent对象外,区别就是要不要传入一个ReferenceQueue队列
Reference(Treferent) {
this(referent,null);
}
Reference(T referent,ReferenceQueue<?super T>queue){
this.referent =referent;
this.queue = (queue==null)? ReferenceQueue.NULL:queue;
}
ReferenceQueue队列的作用就是Reference引用的对象被回收时,Reference对象能否进入到pending队列,最终由ReferenceHander线程处理后,Reference就被放到了这个队列里面(Cleaner对象除外,后面有一篇专门讲Cleaner对象源码),然后我们就可以在这个ReferenceQueue里拿到reference,执行我们自己的操作(至于什么操作就看你想怎么用了),所以这个队列起到一个对象被回收时通知的作用;
如果不带ReferenceQueue的话,要想知道Reference持有的对象是否被回收,就只有不断地轮训reference对象,通过判断里面的get是否为null(phantomReference对象不能这样做,其get始终返回null,因此它只有带queue的构造函数).这两种方法均有相应的使用场景,取决于实际的应用.如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收.而ThreadLocalMap,则采用判断get()是否为null来作处理;
对于带ReferenceQueue参数的构造方法,如果传入的队列为null,那么就会给成员变量queue赋值为ReferenceQueue.NULL队列,这个NULL是ReferenceQueue对象的一个继承了ReferenceQueue的内部类,它重写了入队方法enqueue,这个方法只有一个操作,直接返回 false,也就是这个对列不会存取任何数据,它起到状态标识的作用;
Reference的重要成员变量
//在构造方法传入java对象最后就赋值给了referent,也就是它引用到的对象
private Treferent; /* Treated specially by GC */
//pending队列, pending成员变量与后面的discovered对象一起构成了一个pending单向链表,注意这个成员变量是一个静态对象,所以是全局唯一的,pending为链表的头节点,discovered为链表当前Reference节点指向下一个节点的引用,这个队列是由jvm的垃圾回收器构建的,当对象除了被reference引用之外没有其它强引用了,jvm的垃圾回收器就会将指向需要回收的对象的Reference都放入到这个队列里面(好好理解一下这句话,注意是指向要回收的对象的Reference,要回收的对象就是Reference的成员变量refernt持有的对象,是refernt持有的对象要被回收,而不是Reference对象本身),这个队列会由ReferenceHander线程来处理(ReferenceHander线程是jvm的一个内部线程,它也是Reference的一个内部类,它的任务就是将pending队列中要被回收的Reference对象移除出来,如果Reference对象在初始化的时候传入了ReferenceQueue队列,那么就把从pending队列里面移除的Reference放到它自己的ReferenceQueue队列里(为什么是它自己的?pending队列是全局唯一的队列,但是Reference的queue却不是,它是在构造方法里面指定的,前面说过这里Cleaner对象是个特例),如果没有ReferenceQueue队列,那么其关联的对象就不会进入到Pending队列中,会直接被回收掉,除此之外ReferenceHander线程还会做一些其它操作,后面会讲到
/* List of References waiting to beenqueued. The collector adds
* References to this list, while theReference-handler thread removes
* them. This list is protected by the above lock object. The
* list uses the discovered field to linkits elements.
*/
privatestatic Reference<Object>pending =null;
//与成员变量pending一起组成pending队列,指向链表当前节点的下一个节点
/* When active: next element in a discovered reference listmaintained by GC (or this if last)
* pending: next element in thepending list (or null if last)
* otherwise: NULL
*/
transientprivate Reference<T>discovered; /* used by VM */
//ReferenceQueue队列,这就是我们前面提到的起到通知作用的ReferenceQueue,需要注意的是ReferenceQueue并不是一个链表数据结构,它只持有这个链表的表头对象header,这个链表是由Refence对象里面的next成员变量构建起来的,next也就是链表当前节点的下一个节点(只有next的引用,它是单向链表),所以Reference对象本身就是一个链表的节点,这个链表数据来源前面讲pending队列的时候已经提到了,它是由ReferenceHander线程从pending队列中取的数据构建的(需要注意的是,这个对象并不是一个全局的,它是在构造方法里面传入进来的,所以Reference对象需要进入那个队列是我们自己指定的,也有特例,如FinalReference,Cleaner就是一个内部全局唯一的,无法指定,后面有两篇专门讲他们俩的源码),一旦Reference对象放入了队列里面,那么queue就会被设置为ReferenceQueue.ENQUEUED,来标识当前Reference已经进入到队里里面了;
volatileReferenceQueue<?super T>queue;
//用来与queue成员变量一同组成ReferenceQueue队列,见上面queue的说明;
/* When active: NULL
* pending: this
* Enqueued: next reference inqueue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
Reference next;
//lock成员变量是pending队列的全局锁,如果你搜索这个lock变量会发现它只在ReferenceHander线程run方法里面用到了,不要忘了jvm垃圾回收器线程也会操作pending队列,往pending里面添加Reference对象,所以需要加锁;
/* Object used to synchronize with thegarbage collector. The collector
* must acquire this lock at the beginningof each collection cycle. It is
* therefore critical that any code holdingthis lock complete as quickly
* as possible, allocate no new objects,and avoid calling user code.
*/
staticprivateclass Lock { };
privatestatic Locklock =new Lock();
这里一定要理解pending队列,什么样的Reference对象会进入这个队列。
进入这个队列的Reference对象需要满足两个条件:
1. Reference所引用的对象已经不存在其它强引用;
2. Reference对象在创建的时候,指定了ReferenceQueue;
Reference状态及其转换
如果去查看Reference源码,会发现这个类的开头有一段很长的注释,说明了Reference对象的四种状态:
/* 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.
*/
如果你没有耐心看完这段注释,请直接往后看:
1. Active:活动状态,对象存在强引用状态,还没有被回收;
2. Pending:垃圾回收器将没有强引用的Reference对象放入到pending队列中,等待ReferenceHander线程处理(前提是这个Reference对象创建的时候传入了ReferenceQueue,否则的话对象会直接进入Inactive状态);
3. Enqueued:ReferenceHander线程将pending队列中的对象取出来放到ReferenceQueue队列里;
4. Inactive:处于此状态的Reference对象可以被回收,并且其内部封装的对象也可以被回收掉了,有两个路径可以进入此状态,
路径一:在创建时没有传入ReferenceQueue的Reference对象,被Reference封装的对象在没有强引用时,指向它的Reference对象会直接进入此状态;
路径二、此Reference对象经过前面三个状态后,已经由外部从ReferenceQueue中获取到,并且已经处理掉了。
Reference对象的状态只需要通过成员变量next和queue来判断:
1. Active: next=null
2. Pending: next = this ,queue = ReferenceQueue
3. Enqueued: queue =ReferenceQueue.ENQUEUED
4. Inactive: next = this ,queue = ReferenceQueue.NULL;
以下是Reference对象状态转换图,用word画的,将就着看吧:
特例还是要拿出来单独说,上面的图不适用描述Cleaner对象,Cleaner对象是没有Enqueue状态的,它经过HandReference处理时执行其clean方法清理,然后就直接进入了inactive状态了;
下面简单看一下他们的源码实现,如果你理解了上面所有内容,下面的源码理解起来非常简单,我只做简单的注释说明:
ReferenceHandler线程源码解析
ReferenceHandler线程是一个拥有最高优先级的守护线程,它是Reference类的一个内部类,在Reference类加载执行cinit的时候被初始化并启动;它的任务就是当pending队列不为空的时候,循环将pending队列里面的头部的Reference移除出来,如果这个对象是个Cleaner实例,那么就直接执行它的clean方法来执行清理工作;否则放入到它自己的ReferenceQueue里面;
所以这个线程是pending队列与ReferenceQueue的桥梁;
/* High-priority thread to enqueue pending References*/
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
//从pending中移除的Reference对象
Reference<Object> r;
//此处需要加全局锁,因为除了当前线程,gc线程也会操作pending队列
synchronized (lock) {
//如果pending队列不为空,则将第一个Reference对象取出
if (pending != null) {
//缓存pending队列头节点
r = pending;
//将头节点指向discovered,discovered为pending队列中当前节点的下一个节点,这样就把第一个头结点出队了
pending = r.discovered;
//将当前节点的discovered设置为null;当前节点出队,不需要组成链表了;
r.discovered = null;
} else {//如果pending队列为空,则等待
try {
try {
lock.wait();
} catch(OutOfMemoryError x) { }
} catch(InterruptedException x) { }
continue;
}
}
// 如果从pending队列出队的r是一个Cleaner对象,那么直接执行其clean()方法执行清理操作;
if (r instanceof Cleaner) {
((Cleaner)r).clean();
//注意这里,这里已经不往下执行了,所以Cleaner对象是不会进入到队列里面的,给它设置ReferenceQueue的作用是为了让它能进入Pending队列后被ReferenceHander线程处理;
continue;
}
//将对象放入到它自己的ReferenceQueue队列里
ReferenceQueue<Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
//以下是ReferenceHander线程初始化并启动的操作
static {
ThreadGroup tg =Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
//线程名称为Reference Handler
Thread handler = newReferenceHandler(tg, "Reference Handler");
/* If there were a special system-onlypriority greater than
* MAX_PRIORITY, it would be used here
*/
//线程有最高优先级
handler.setPriority(Thread.MAX_PRIORITY);
//设置线程为守护线程;
handler.setDaemon(true);
handler.start();
}
ReferenceQueue源码解析
ReferenceQueue队列是一个单向链表,ReferenceQueue里面只有一个header成员变量持有队列的队头,Reference对象是从队头做出队入队操作,所以它是一个后进先出的队列
public class ReferenceQueue<T> {
public ReferenceQueue() { }
//内部类,它是用来做状态识别的,重写了enqueue入队方法,永远返回false,所以它不会存储任何数据,见后面的NULL和ENQUEUED两个标识成员变量
private static class Null<S> extendsReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
//当Reference对象创建时没有指定queue或Reference对象已经处于inactive状态
staticReferenceQueue<Object> NULL = new Null<>();
//当Reference已经被ReferenceHander线程从pending队列移到queue里面时
static ReferenceQueue<Object> ENQUEUED = new Null<>();
staticprivate class Lock { };
//出队入队时对队列加锁
privateLock lock = new Lock();
//队列头
privatevolatile Reference<? extends T> head = null;
//队列长度
private long queueLength = 0;
//入队操作,ReferenceHander调用此方法将Reference放入到队列里
boolean enqueue(Reference<? extends T> r) { /* Called only byReference class */
//加锁操作队列
synchronized(lock) {
//如果Reference创建时没有指定队列或Reference对象已经在队列里面了,则直接返回
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
//只有r的队列是当前队列才允许入队
assert queue == this;
//将r的queue设置为ENQUEUED状态,标识Reference已经入队
r.queue = ENQUEUED;
//从队列头部入队
r.next = (head == null) ? r : head;
head = r;
//队列里面对象数量+1
queueLength++;
//如果r是一个FinalReference实例,那么将FinalReference数量也+1
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
/**Vm.addFinalRefCount(int n)方法的源码
publicstatic void addFinalRefCount(int n) {
// The caller must hold lock to synchronize the update.
finalRefCount += n;
if (finalRefCount > peakFinalRefCount) {
peakFinalRefCount = finalRefCount;
}
}
可以通过sun.misc.VM.getFinalRefCount()和sun.misc.VM.getPeakFinalRefCount()来获取FinalReference对象的当前数量和峰值数量
***/
}
//唤醒出队操作的等待线程
lock.notifyAll();
return true;
}
}
//Reference对象出队操作,将头部第一个对象移出队列,并将队列长度-1
@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 inReference
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
//将队列头部第一个对象从队列中移除出来,如果队列为空则直接返回null(此方法不会被阻塞)
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
//将头部第一个对象移出队列并返回,如果队列为空,则等待timeout时间后,返回null,这个方法会阻塞线程
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;
for (;;) {
lock.wait(timeout);
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) return null;
}
}
}
public Reference<? extends T> remove() throws InterruptedException{
return remove(0);
}
}