关于java的引用类型以及对对象的回收涉及到以下几个类,Reference、Reference子类(SoftReference、WeakReference、PhantomReference)以及ReferenceQueue,这篇文章就重点讲解以下。
一. java对象的引用类型:
-
1. 强引用(FinalReference)
强引用就是我们经常使用的引用,其写法如下:
StringBuffer buffer = new StringBuffer();
面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
-
2. 软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。如果全部释放完这些对象之后,内存还不足,才会抛出OutOfMemory错误。
由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。
SoftReference softWidget = new SoftReference(widget);
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,关于引用队列我们在接下来讲
-
3. 弱引用(WeakReference)
弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下:
WeakReference weakWidget = new WeakReference(widget);
使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
其中一个应用就是WeakHashMap,WeakHashMap和HashMap几乎一样,唯一的区别就是它的键(不是值!!!)使用WeakReference引用。当WeakHashMap的键标记为垃圾的时候,这个键对应的条目就会自动被移除。这就避免了上面不需要的Widget对象手动删除的问题。这里是和ReferenceQueue联合使用
另外一个应用就是ThreadLocal中也应用到了WeakReference。ThreadLocal中并没有将ReferenceQueue联合使用
-
4. 虚引用(PhantomReference)
在Object类里面有个finalize方法,其设计的初衷是在一个对象被真正回收之前,可以用来执行一些清理的工作。但是问题在于垃圾回收器的运行时间是不固定的,所以这些清理工作的实际运行时间也是不能预知的。虚引用(phantom reference)可以解决这个问题。在创建幽灵引用PhantomReference的时候必须要指定一个引用队列。当一个对象的finalize方法已经被调用了之后,这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了。
虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
二. 引用队列(ReferenceQueue)
上面的这些概念中都提到了ReferenceQueue,下面我们就来看看ReferenceQueue是什么.
首先看源码中的介绍
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中
查看源代码会发现它很简单,实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的Reference。
那么这个ReferenceQueue是如何使用的呢?我们接着看与它关系紧密的Reference。
引用(Reference)
同样先看源码中对这个类的介绍:
Abstract base class for reference objects. This class defines the operations common to all reference objects. Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.
这里可以看到,这个类是和垃圾回收紧密相关的,上面介绍的四个子类(四种引用类型)垃圾回收的方式不同。我们来具体看看Reference的实现。
Reference实例有四种状态(建议直接看源码,翻译的话总会感觉少了点东西)
* 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.
从数据结构上看,Reference类内部主要的成员有
private T referent; /* Treated specially by GC */
volatile ReferenceQueue queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
Reference next;
/* When active: next element in a discovered reference list maintained by GC (or this if last)
* pending: next element in the pending list (or null if last)
* otherwise: NULL
*/
transient private Reference discovered; /* used by VM */
/* List of References waiting to be enqueued. The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object. The
* list uses the discovered field to link its elements.
*/
private static Reference
这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
Queue的另一个作用是可以区分不同状态的Reference。Reference有4种状态,不同状态的reference其queue和next的规则如下不同:
* 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.
*
说说自己的理解:就是Reference有四种状态,每一种状态都都相应的queue和next变量,建议看源码部分的对应关系时要仔细理解。
ReferenceHandler线程
这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
ReferenceHandler线程要做的是将pending对象enqueue,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULL,Handler线程判断queue为ReferenceQueue.NULL则不进行操作,只有非ReferenceQueue.NULL的queue才会将Reference进行enqueue。
ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。
在WeakHashMap则在内部提供了一个非NULL的ReferenceQueue
private final ReferenceQueue queue = new ReferenceQueue();
在 WeakHashMap 添加一个元素时,会使用 此queue来做监听器。见put方法中的下面一句:
tab[i] = new Entry(k, value, queue, h, e);
这里Entry是一个内部类,继承了WeakReference
class Entry extends WeakReference implements Map.Entry
WeakHashMap的 put, size, clear 都会间接或直接的调用到 expungeStaleEntries()方法。
expungeStaleEntries顾名思义,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。
参考链接
1.并发编程 | ThreadLocal源码深入分析
2.深入分析 ThreadLocal 内存泄漏问题