Java源码阅读------Reference

描述

Reference是PhantomReference(虚引用),WeakReference(弱引用),SoftReference(软引用)的父类。位于java.lang.ref包中,是一个使用泛型的抽象类。它定义了所有引用对象的一般操作,与垃圾回收机制(gc)息息相关。

四种状态

每一个Reference实例都处在四种可能的内在状态之一。

Active

1
2
3
4
5
6
7
8
/*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.
*/

活跃状态,新创建的实例,或是还有强引用的实例是处于Active状态,在gc处理时,会根据是否注册引用队列来区分处理。注册了的会将其放到pending状态,没有注册的直接转到Inactive。

Pending

1
2
3
4
/*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-handler这个线程的处理,将其入队进入引用队列ReferenceQueue中,进入Enqueued状态,没有注册引用队列的自然不会进入这一状态。

Enqueued

1
2
3
4
5
/*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.
*/

队内状态,在这一状态下,引用对象将会处于ReferenceQueue中,如果从中移除后将会进入Inactive状态,同理没有注册ReferenceQueue的引用对象无法进入这一状态。

Inactive

1
2
3
/*Inactive: Nothing more to do.  Once an instance becomes Inactive its
* state will never change again.
*/

不活跃状态,最终状态,位于这一状态的实例将会被gc回收。

内部实现

referent

1
private T referent;         /* Treated specially by GC */

对内部对象的保存引用,使用了泛型,gc会特别处理。

1
2
3
public T get() {
return this.referent;
}

相关的get方法获取了包装的引用。

1
2
3
public void clear() {
this.referent = null;
}

使用clear方法可以对referent 清理,之后其将不会再被加入引用队列,gc在回收清理时并不执行该方法,而是更直接的清理。

queue

1
volatile ReferenceQueue<? super T> queue;

对于需要通知机制的,用于储存传入的引用队列,使用通配符泛型,内部可以是相关的子类。

1
2
3
public boolean isEnqueued() {
return (this.queue == ReferenceQueue.ENQUEUED);
}

这一方法可以判断queue中是否含有引用。

1
2
3
public boolean enqueue() {
return this.queue.enqueue(this);
}

将注册了引用队列的引用加入其对应的队列中。

构造函数

有两种构造函数分别实现需要引用队列的和不需要引用队列的。

1
2
3
4
5
6
7
8
Reference(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
8
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<>();

初始化的静态处理

线程

线程:首先,线程不是进程,一个进程有很多子线程,线程是进程中的一个执行流程,只是轮换执行的过程不是同时。多进程是操作系统同时运行多个程序的方法,多线程是程序同时运行多个处理的方法,这里的同步是我们的感觉同步,实际上还是碎片化时间轮流处理。在java中以Thread类实现。
线程组:用于管理线程,是线程的组合,除了初始线程组外,每个线程组都有一个父线程组,所有的线程组呈树状分布,线程组中除了包含线程还会包含线程组。
线程优先级:根据线程处理的事务不同给它们分配不同的优先级,优先级越高越可能被执行,在Thread中定义最小(MIN_PRIORITY)为1,最大(MAX_PRIORITY)为10,默认的优先级(NORM_PRIORITY)为5。
线程分类:一般分为守护线程与用户线程,两者的区别主要体现在java虚拟机的处理上,守护线程依赖非守护线程而存在,如果不存在非守护线程只剩下守护线程,那么虚拟机会直接将其终止并退出(工作完成)。

静态处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}

首先获取了当前正在执行的线程的线程组。之后使用循环获得根线程组,应该是System线程组。之后初始化了一个ReferenceHandler线程,这个是Reference的一个内部类,将名为Reference Handler的线程注册到根线程组中,并设置优先级为最高的10,并设置为守护进程运行,必须在运行之前设置。
最后说一下SharedSecrets类,这个类的实现只是包含了许多接口的get与set,简单的说是实现了私有方法的共享,而不必使用反射去实现,在这里其实现了JavaLangRefAccess这个接口,共享了tryHandlePending这一私有方法。

ReferenceHandler

ReferenceHandler是一个静态内部类,继承自Thread是一个线程,负责将处于pending状态的引用加入引用队列。

构造函数

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

调用Thread中的方法将线程加入到线程组中。

静态处理

1
2
3
4
5
6
7
static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don't get into trouble later in the run loop if there's
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}

使用ensureClassInitialized方法完成InterruptedException与Cleaner两个类的初始化加载。

ensureClassInitialized

很简单的实现,通过传入的类获取类名加载对应的类,参数中的true为实现初始化,getClassLoader为获取传入的类中的类加载器。

1
2
3
4
5
6
7
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);
}
}

run

这个方法大家应该很熟悉,线程的运行方法包含运行进行的操作。

1
2
3
4
5
public 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
2
static private class Lock { }
private static Lock lock = new Lock();

这里定义了一个Object用于线程同步,具体可以参考Object中的notify与wait方法Java源码阅读——Object

数据结构

1
2
3
private static Reference<Object> pending = null;
transient private Reference<T> discovered; /* used by VM */
Reference next;

在实现中出现了两个数据结构,一个是负责描述入队后引用的单向列表由next指向下一个Reference对象,具体的是应用在ReferenceQueue实现,还有一个是Pending队列用于存储所有Pending状态的Reference,由discovered与pending实现分别指向队列首部与相应节点的下一个元素。

详细实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// 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...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}

判断pending是否为空,为空则根据waitForNotify参数来判断是否立即返回。若不为空,用r取出Pending中的首部元素,重新将Pending指向其下一个元素,判断r是否为Cleaner类方便进行单独的处理,如果是就用c来存储,跳出同步操作,根据c中的数据进行单独的Cleaner清理,最后将获得的Pending状态的引用加入到对应的ReferenceQueue中。注意在同步操作部分可能会出现内存溢出,所以使用了try-catch,出现异常时默认返回true。

小结

Reference的使用很灵活,其运行过程与gc处理息息相关,源码中的思想还是很值得研究的。