描述
Reference是PhantomReference(虚引用),WeakReference(弱引用),SoftReference(软引用)的父类。位于java.lang.ref包中,是一个使用泛型的抽象类。它定义了所有引用对象的一般操作,与垃圾回收机制(gc)息息相关。
四种状态
每一个Reference实例都处在四种可能的内在状态之一。
Active
1 | /*Active: Subject to special treatment by the garbage collector. Some |
活跃状态,新创建的实例,或是还有强引用的实例是处于Active状态,在gc处理时,会根据是否注册引用队列来区分处理。注册了的会将其放到pending状态,没有注册的直接转到Inactive。
Pending
1 | /*Pending: An element of the pending-Reference list, waiting to be |
等待状态,在pending状态中,引用会等待Reference-handler这个线程的处理,将其入队进入引用队列ReferenceQueue中,进入Enqueued状态,没有注册引用队列的自然不会进入这一状态。
Enqueued
1 | /*Enqueued: An element of the queue with which the instance was |
队内状态,在这一状态下,引用对象将会处于ReferenceQueue中,如果从中移除后将会进入Inactive状态,同理没有注册ReferenceQueue的引用对象无法进入这一状态。
Inactive
1 | /*Inactive: Nothing more to do. Once an instance becomes Inactive its |
不活跃状态,最终状态,位于这一状态的实例将会被gc回收。
内部实现
referent
1 | private T referent; /* Treated specially by GC */ |
对内部对象的保存引用,使用了泛型,gc会特别处理。1
2
3public T get() {
return this.referent;
}
相关的get方法获取了包装的引用。1
2
3public void clear() {
this.referent = null;
}
使用clear方法可以对referent 清理,之后其将不会再被加入引用队列,gc在回收清理时并不执行该方法,而是更直接的清理。
queue
1 | volatile ReferenceQueue<? super T> queue; |
对于需要通知机制的,用于储存传入的引用队列,使用通配符泛型,内部可以是相关的子类。1
2
3public boolean isEnqueued() {
return (this.queue == ReferenceQueue.ENQUEUED);
}
这一方法可以判断queue中是否含有引用。1
2
3public boolean enqueue() {
return this.queue.enqueue(this);
}
将注册了引用队列的引用加入其对应的队列中。
构造函数
有两种构造函数分别实现需要引用队列的和不需要引用队列的。1
2
3
4
5
6
7
8Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
说明一下在ReferenceQueue的定义中有两个静态变量NULL与ENQUEUED分别表示没有设置引用队列和已经入队的两种状态。1
2
3
4
5
6
7
8private 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<>();
初始化的静态处理
线程
线程:首先,线程不是进程,一个进程有很多子线程,线程是进程中的一个执行流程,只是轮换执行的过程不是同时。多进程是操作系统同时运行多个程序的方法,多线程是程序同时运行多个处理的方法,这里的同步是我们的感觉同步,实际上还是碎片化时间轮流处理。在java中以Thread类实现。
线程组:用于管理线程,是线程的组合,除了初始线程组外,每个线程组都有一个父线程组,所有的线程组呈树状分布,线程组中除了包含线程还会包含线程组。
线程优先级:根据线程处理的事务不同给它们分配不同的优先级,优先级越高越可能被执行,在Thread中定义最小(MIN_PRIORITY)为1,最大(MAX_PRIORITY)为10,默认的优先级(NORM_PRIORITY)为5。
线程分类:一般分为守护线程与用户线程,两者的区别主要体现在java虚拟机的处理上,守护线程依赖非守护线程而存在,如果不存在非守护线程只剩下守护线程,那么虚拟机会直接将其终止并退出(工作完成)。
静态处理
1 | static { |
首先获取了当前正在执行的线程的线程组。之后使用循环获得根线程组,应该是System线程组。之后初始化了一个ReferenceHandler线程,这个是Reference的一个内部类,将名为Reference Handler的线程注册到根线程组中,并设置优先级为最高的10,并设置为守护进程运行,必须在运行之前设置。
最后说一下SharedSecrets类,这个类的实现只是包含了许多接口的get与set,简单的说是实现了私有方法的共享,而不必使用反射去实现,在这里其实现了JavaLangRefAccess这个接口,共享了tryHandlePending这一私有方法。
ReferenceHandler
ReferenceHandler是一个静态内部类,继承自Thread是一个线程,负责将处于pending状态的引用加入引用队列。
构造函数
1 | ReferenceHandler(ThreadGroup g, String name) { |
调用Thread中的方法将线程加入到线程组中。
静态处理
1 | static { |
使用ensureClassInitialized方法完成InterruptedException与Cleaner两个类的初始化加载。
ensureClassInitialized
很简单的实现,通过传入的类获取类名加载对应的类,参数中的true为实现初始化,getClassLoader为获取传入的类中的类加载器。1
2
3
4
5
6
7private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
run
这个方法大家应该很熟悉,线程的运行方法包含运行进行的操作。1
2
3
4
5public void run() {
while (true) {
tryHandlePending(true);
}
}
实现也很直观就是跑tryHandlePending这个函数。
tryHandlePending
应该算是这个类中比较有意思的一个函数了,负责将处于Pending状态的Reference加入到引用队列queue中。若返回为true表示还有处于Pending状态的Reference,为false则没有,可以通过返回值来操控其运行。waitForNotify参数表示是否等待Notify方法,当没有Pending状态的引用时waitForNotify为true则等待Notify方法或线程的中断interrupted,为false就立即结束。
Object锁
1 | static private class Lock { } |
这里定义了一个Object用于线程同步,具体可以参考Object中的notify与wait方法Java源码阅读——Object
数据结构
1 | private static Reference<Object> pending = null; |
在实现中出现了两个数据结构,一个是负责描述入队后引用的单向列表由next指向下一个Reference对象,具体的是应用在ReferenceQueue实现,还有一个是Pending队列用于存储所有Pending状态的Reference,由discovered与pending实现分别指向队列首部与相应节点的下一个元素。
详细实现
1 | static boolean tryHandlePending(boolean waitForNotify) { |
判断pending是否为空,为空则根据waitForNotify参数来判断是否立即返回。若不为空,用r取出Pending中的首部元素,重新将Pending指向其下一个元素,判断r是否为Cleaner类方便进行单独的处理,如果是就用c来存储,跳出同步操作,根据c中的数据进行单独的Cleaner清理,最后将获得的Pending状态的引用加入到对应的ReferenceQueue中。注意在同步操作部分可能会出现内存溢出,所以使用了try-catch,出现异常时默认返回true。
小结
Reference的使用很灵活,其运行过程与gc处理息息相关,源码中的思想还是很值得研究的。