Java Reference源码解析

Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互。即可以使用Reference对象来引用其它对象,但是最后还是会被垃圾收集器回收。程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更。 

Java提供了四种不同类型的引用,引用级别从高到低分别为FinalReference,SoftReference,WeakReference,PhantomReference。其中FinalReference不对外提供使用。每种类型对应着不同级别的可达性。

简介

强引用FinalReference

强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如Object obj = new Object();中,obj为强引用。

软引用SoftReference

软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出OOM异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。

SoftReference<Object> softRef = new SoftReference<Object>(new Object());

弱引用WeakReference

弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见WeakHashMap等。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object());

虚引用PhantomReference

虚引用,该引用必须和引用队列(ReferenceQueue)一起使用,一般用于实现追踪垃圾收集器的回收动作,比如在对象被回收的时候,会调用该对象的finalize方法,在使用虚引用可以实现该动作,也更加安全。

Object obj = new Object();

ReferenceQueue<Object> refQueue = new ReferenceQueue<>();

PhantomReference<Object> phantom = new PhantomReference<Object>(obj, refQueue);

ReferenceQueue

该队列作为引用中的一员,可以和上述三种引用类型组合使用,该队列的作用是:创建Reference时,将Queue注册到Reference中,当该Reference所引用的对象被垃圾收集器回收时,会将该Reference放到该队列中,相当于一种通知机制。

示例 Demo1:

ReferenceQueue queue = new ReferenceQueue();

WeakReference reference = new WeakReference(new Object(), queue);

System.out.println(reference);

System.gc();

Reference reference1 = queue.remove();

System.out.println(reference1);

源码分析

Reference和ReferenceQueue

Reference内部有几个比较重要的属性

// 用于保存对象的引用,GC会根据不同Reference来特别对待

private T referent;

// 如果需要通知机制,则保存的对对应的队列

ReferenceQueue<? super T> queue;

/* 这个用于实现一个单向循环链表,用以将保存需要由ReferenceHandler处理的引用 */

Reference next;

static private class Lock { };

// 锁,用于同步pending队列的进队和出队

private static Lock lock = new Lock();

// 此属性保存一个PENDING的队列,配合上述next一起使用

private static Reference pending = null;

状态图

内部类ReferenceHandler

ReferenceHandler作为Reference的静态内部类,用于实现将pending队列里面的Reference实例依次添加到不同的ReferenceQueue中(取决于Reference里面的queue)。该pending的元素由GC负责加入。

注:这里对pending队列进行加锁,个人认为是因为GC线程可能和ReferenceHandler所在的线程并发执行,如GC采用CMS并发收集的时候。

如下代码所示

// 此线程在静态块中启动,即一旦使用了Reference,则会启动该线程

private static class ReferenceHandler extends Thread {

public void run() {

for (;;) {

Reference r;

synchronized (lock) {

if (pending != null) {

r = pending;

Reference rn = r.next;

// 从pending中取下一个元素,如果后继为空,则next指向自身 pending = (rn == r) ? null : rn;

r.next = r;

} else {

try {

// 没有则等待,后续加入元素会调用lock.notify唤醒

lock.wait();

} catch (InterruptedException x) { }

continue;

}

}

// ...

ReferenceQueue q = r.queue;

// 如果该Reference注册了对应的Queue,则加入到该Queue中

if (q != ReferenceQueue.NULL) q.enqueue(r);

}

}

}

ReferenceQueue属性

// 用于标识没有注册Queue

static ReferenceQueue NULL = new Null();

// 用于标识已经处于对应的Queue中

static ReferenceQueue ENQUEUED = new Null();

static private class Lock { };

/* 互斥锁,用于同步ReferenceHandler的enqueue和用户线程操作的remove和poll出队操作 */

private Lock lock = new Lock();

// 队列

private volatile Reference<? extends T> head = null;

// 队列中的元素个数

private long queueLength = 0;

ReferenceQueue.enqueue

只会通过Reference里要调用该方法,用于将Reference放入到当前队列中

boolean enqueue(Reference<? extends T> r) {

synchronized (r) {

// 判断是否已经入队了

if (r.queue == ENQUEUED) return false;

synchronized (lock) {

r.queue = ENQUEUED;

// 单向循环

r.next = (head == null) ? r : head;

head = r;

queueLength++;

if (r instanceof FinalReference) {

sun.misc.VM.addFinalRefCount(1);

}

// 通知当前挂起的线程(调用remove时有可能会挂起)

lock.notifyAll();

return true;

}

}

}

ReferenceQueue.remove

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 (;;) {

// 否则等待,由enqueue时notify唤醒

lock.wait(timeout);

r = reallyPoll();

if (r != null) return r;

if (timeout != 0) return null;

}

}

}

具体执行流程

以上述示例Demo1作为分析

// 创建一个引用队列

ReferenceQueue queue = new ReferenceQueue();

// 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null

WeakReference reference = new WeakReference(new Object(), queue);

System.out.println(reference);

// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING

System.gc();

/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */

/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */

Reference reference1 = queue.remove();

System.out.println(reference1);

应用 - WeakHashMap

WeakHashMap在使用上和HashMap类型,都是Hash + 链表解决冲突,唯一不同点在于前者的Key是使用虚引用来实现的,即当进行垃圾回收的时候,就是被回收掉,此时WeakHashMap会在下次操作的时候,根据被回收掉的Key,从Map里面移除掉。

Entry

当创建Entry的时候,会注册进当前Map属性的queue,当key被回收后,则该Entry会被放入到queue中,每当操作Map的时候,才会将原有的Value清除掉。(由expungeStaleEntries方法来进行,并且没有启动一个单独的线程来处理,没有必要,这样子简化了逻辑以及避免锁的开销)

// 外部WeakHashMap属性

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

/* 这里采用了集成WeakReference而不是直接使用,是因为当被回收的时候,具体的Key是不知道的,这里需要往WeakReference额外加入一些属性,以便在被回收后通知时,能够定位到具体的Key/value */

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {

// 这里属性不能加入key,否则会导致存在强引用而不能被视为WeakReference回收掉

V value;

int hash;

Entry<K,V> next;

Entry(Object key, V value,

ReferenceQueue<Object> queue,

int hash, Entry<K,V> next) {

super(key, queue);

this.value = value;

this.hash = hash;

this.next = next;

}

// ...

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是 Java Reference源码解析 的全部内容, 来源链接: utcz.com/p/211878.html

回到顶部