Java引用类型

在jdk1.2之后,位于java.lang.ref包下有Reference的实现SoftReference(软引用),WeakReference(弱引用),PhantomReference(虚引用),FinalReference(即强引用StrongReference),在jdk1.2之前所有的引用皆为强引用。Reference作为一个abstract修饰的类,是所有引用类型的基类,此类定义了常用于所有引用对象的操作。因为引用对象(*Reference)是通过与垃圾回收器的密切合作来实现的,所以不能由程序员直接为此类(Reference)创建子类。Reference类型的使用主要是为了增强jvm内存管理功能,通过使用不同的引用类型,程序员可以在一定程度上与GC交互,通过定义不同对象的引用类型改善GC的效率。被强引用类型引用的对象即使在OOM的情况下也不会被GC掉,软引用的对象在内存吃紧的情况下触发的GC过程中会被清除,弱引用的对象只要触动了GC就会被清除,虚引用的对象引用并不影响对象的生命时间,虚引用主要用来跟踪对象被垃圾回收器回收的活动。

Reference预读

需要了解引用怎么作用的,需先了解它的两个关键类,ReferenceReferenceQueue

Reference

作为所有引用类型的基类,Reference定义了所有引用对象的常用操作:

1
2
3
4
void clear();           // 清除此引用对象。调用此方法不会导致对象被加入队列。
boolean enqueue(); // 将此引用对象添加到引用对象已向其注册的队列(如果有)。如果成功将此引用对象加入队列中,则返回 true;如果它已经加入队列或者在创建时没有在队列中注册它,则返回 false。
T get(); // 返回此引用对象的指示对象。如果此引用对象已经由程序或垃圾回收器清除,则此方法将返回 null。
boolean isEnqueue(); // 由程序或垃圾回收器通知是否已将此引用对象加入队列。当且仅当此引用对象已经加入队列时返回 true。

每个引用都有以下属性:

1
2
3
4
private T referent;         /* Treated specially by GC */       // 定义引用的对象
volatile ReferenceQueue<? super T> queue; // 引用队列
Reference next; // 作为引用队列的下一个元素指针
transient private Reference<T> discovered; /* used by VM */

ReferenceQueue

引用对象队列,是用来保存注册为引用对象的队列。在检测到引用对象的可到达性被变更后,垃圾回收器会将已注册的引用对象添加到该队列中。引用队列是一个线程安全的单向队列类型,通过volatile声明的Reference类型的head指针作为队列的入口,属性queueLength记录队列长度,对该队列的所有操作都会通过synchronized关键字锁住队列本身,即每个引用队列都具备一个锁维护出入队的线程安全:

1
2
static private class Lock { };
private Lock lock = new Lock();

ReferenceQueue的所有入队方法为friendly修饰的,即所有引用队列元素的入队操作都是由该队列所在的引用对象完成的:

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
// ReferenceQueue的入队(Called only by Reference class)
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
// 仅在 Reference中被调用(Reference.enqueue())
public boolean enqueue() {
return this.queue.enqueue(this);
}

除了被引用掌控的入队操作,ReferenceQueue还具有以下操作:

1
2
3
public Reference<? extends T> poll();               // 轮询此队列,查看是否存在可用的引用对象。如果存在一个立即可用的对象,则从该队列中移除此对象并返回。否则此方法立即返回 null。
public Reference<? extends T> remove(long timeout); // 移除此队列中的下一个引用对象,阻塞到有一个对象变得可用或者给定的超时期(ms)满了为止。如果超时值为零,则无限期地阻塞。
public Reference<? extends T> remove() // 移除此队列中的下一个引用对象,阻塞到某个对象变得可用为止。即超时值被设为0。

四种引用类型

SoftReference 软引用

软引用对象,在响应内存需要时,由垃圾回收器决定是否清除此对象。软引用对象最常用于实现内存敏感的缓存。假定垃圾回收器确定在某一时间点某个对象是软可到达对象,这时,它可以选择自动清除针对该对象的所有软引用,以及通过强引用链,从其可以到达该对象的针对任何其他软可到达对象的所有软引用。在同一时间或晚些时候,它会将那些已经向引用队列注册的新清除的软引用加入队列。软可到达对象的所有软引用都要保证在虚拟机抛出 OutOfMemoryError 之前已经被清除。否则,清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束。然而,虚拟机倾向于反对清除最近访问或使用过的软引用。
此类的直接实例可用于实现简单缓存;该类或其派生的子类还可用于更大型的数据结构,以实现更复杂的缓存。只要软引用的指示对象是强可到达对象,即正在实际使用的对象,就不会清除软引用。例如,通过保持最近使用的项的强指示对象,并由垃圾回收器决定是否放弃剩余的项,复杂的缓存可以防止放弃最近使用的项。

WeakReference 弱引用

弱引用对象,在弱引用对象没有任何强引用的情况下,触发GC时弱引用对象就会被清除。对应使用到弱引用的数据结构有WeakHashMap,该Map的元素类即继承自WeakReference,通常该类型Map也被用来作为缓存。

PhantomReference 虚引用

虚引用对象,在垃圾回收器决定它引用的对象可能要被回收时就会将其添加到队列(构造方法中的那个ReferenceQueue)中,虚引用经常被用于比Java的finalization机制更灵活的方式来调度预验(pre-mortem)清除操作。为了确保可回收的对象仍然保持原状,虚引用的指示对象不能被获取,即虚引用的get方法总是返回null(阻止其指向的几乎被销毁的对象重新复活)。通过虚引用可到达的对象将仍然保持原状,直到所有这类引用都被清除,或者它们都变得不可到达。虚引用和前面的软引用、弱引用不同,虚引用在加入队列时并没有通过垃圾回收器自动清除,它并不影响对象的生命周期。虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动(即前文提及的预验(pre-mortem)清除操作)。

FinalReference(StrongReference)强引用

强引用其实就是默认的引用类型,是指创建一个对象并把这个对象赋给一个引用变量,譬如String a = new String("a is a strong reference.");,则a这个引用即是强引用,虚拟机将这类引用就通过FinalReference包装处理。强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。如果需要强制释放强引用引用的对象,可以将强引用引用到null,那该对象在没有被引用的情况下就能响应GC操作,被清理掉了。

Finalizer

关于 Finalizer类的描述,祥见JVM源码分析之FinalReference完全解读。综合文章描述有以下几点:
1、Finalizer是FinalReference的继承,package(包)访问权限,final不可扩展类。
2、通过类中是否存在一个参数为空,返回void的非空void finalize()方法判断该类是否为一个 Finalizer类。
3、Finalizer类对象在构造函数返回之前调用Finalizer.register方法,可通过配置-XX:-RegisterFinalizersAtInit关闭,并将在对象空间分配好之后调用Finalizer.register,将强引用类型对象引用注册为Finalizer类型引用:

1
2
3
4
5
6
7
8
9
private Finalizer(Object finalizee) {
super(finalizee, queue);
add(); // 即将 this添加到
}

/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}

当通过clone的方式复制一个对象时,如果当前类是一个f类,那么在clone完成时将调用Finalizer.register方法进行注册。

总结

其实 SoftReference, WeakReference 以及 PhantomReference 的构造函数都可以接收一个 ReferenceQueue 对象。当 SoftReference 以及 WeakReference 被清空的同时,也就是 Java 垃圾回收器准备对它们所指向的对象进行回收时,调用对象的finalize()方法之前,它们自身会被加入到这个 ReferenceQueue 对象中,此时可以通过 ReferenceQueue 的poll()方法取到它们。而 PhantomReference 只有当 Java 垃圾回收器对其所指向的对象真正进行回收时,会将其加入到这个 ReferenceQueue 对象中,这样就可以追综对象的销毁情况。

引用类型 取得目标对象的方式 垃圾回收条件 是否可能内存泄露
强引用 直接引用 不回收 可能
软引用 通过get()取得 视内存情况(OOM前) 不可能
弱引用 通过get()取得 有GC即回收 不可能
虚引用 无法获取 不回收 可能

感谢: