在 “ 什么是对象引用 ” 中,我们知道了对象和对象引用的关系,在栈中的对象引用指向堆中的对象。

方法调用结束后,栈中的引用会被清空,但存活于堆中的对象,不会随着方法调用的结束而被清除,因此进程空间可能会被不断创建的对象占满。

Java 中建立了垃圾回收机制来清除不再被使用的对象。

垃圾回收的基本原则是,当存在对象引用指向某个对象时,该对象不会被回收;当没有任何引用指向某个对象时,该对象会回收。

但在某些情况下,我们会希望有些对象不需要被立即回收,或者说从全局来看没有被回收的必要。比如缓存系统的设计,为了提高运行效率,一些未被使用的缓存对象仍可再堆中存活,无需立即回收。

为了满足这种要求,JDK 1.2 版本开始,Java 将对象的引用细分为强引用、软引用、弱引用和虚引用四种级别。

细分的准则是体现在被 GC 回收的优先级上:强引用 > 软引用 > 弱引用 > 虚引用。

4.1 强引用

强引用表示一个对象处于 “ 有用、必须 ” 的状态,是最传统的引用的定义,也就是在程序代码中普遍存在的引用赋值。

如果一个对象具有强引用,那么垃圾回收器绝不会回收。

即使内存空间不足,Java 虚拟机会抛出 OutOfMemoryError 错误,使得程序异常终止,也不会通过回收强引用对象来解决内存不足的问题。

Phone phone = new Phone();

后面三种对象引用都是基于强引用,也就是说,必须先存在一个强引用的对象,然后对这个对象做一些操作,将它变成其他对象引用。

4.2 软引用

软引用表示一个对象处于 “ 有用、但非必须 ” 的状态。

软引用关联着的对象,在系统将要发生内存溢出异常前,才会将这些对象列入回收范围之中进行第二次回收,如果还没有足够的内存,Java 虚拟机才会抛出 OutOfMemoryError 错误。

JDK 1.2 之后提供了 SoftReference 来实现软引用:

Phone phone = new Phone();
SoftReference softReference = new SoftReference(phone);

软引用可以用来实现内存敏感的高速缓存。

将软引用和一个引用队列联合使用,如果软引用引用的对象被垃圾回收器回收,Java 虚拟机会将这个软引用加到与之关联的引用队列中,以便在恰当的时机将该软引用回收。

但由于 GC 线程的优先级较低,通常手动调用 System.gc() 并不会立即执行 GC ,因此软引用所引用的对象并不一定会被立马回收。

因此,通过检查引用队列的方式判断软引用对象是否被回收,从而实现一个具有可用性的高速缓存。

示例:

Phone phone = new Phone();
SoftReference phoneRef = new SoftReference(phone);
// 使用结束
phone = null;
// 再次获取强引用,注意此时 phoneRef.get() 可能为 null
Phone phone = (Phone) phoneRef.get();
// 引入引用队列
ReferenceQueue queue = new ReferenceQueue();
SoftReference phoneRef = new SoftReference(phone, queue); 
// 根据引用队列清除被回收的软引用对象
SoftReference phoneRef = null;
while ((phoneRef = (Phone) queue.poll()) != null) {
    // 清除软引用对象
}
4.3 弱引用

弱引用表示一个对象处于 “ 可能有用,但非必须 ” 的状态。

类似于软引用,但弱引用的级别比软引用更弱。

弱引用关联的对象只能生存到下一次垃圾回收发生为止。一旦垃圾回收器开始工作,无论当前内存是否足够,都会回收掉弱引用关联的对象。

也就是说, GC 线程在扫描它所管辖的内存区域时,一旦发现只具有弱引用的对象,就会进行回收。但由于 GC 是一个优先级较低的线程,因此不一定会很快发现这些只具有弱引用的对象。

弱引用同样可以和一个引用队列联合使用,如果弱引用所引用的对象被垃圾回收器回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

JDK 1.2 之后提供了 WeakReference 来实现弱引用:

Phone phone = new Phone();
WeakReference wealReference = new WeakReference(phone);
4.4 虚引用

虚引用表示一个对象处于 “ 无用” 的状态,意味着等同于没有引用,随时可能被 GC 回收,也被称为 “ 幽灵引用 ” 或者 “ 幻影引用 ”,是最弱的一种引用关系。

一个对象是否由虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。

对一个对象设置虚引用的唯一目的是为了能在这个对象被垃圾回收器回收时收到一个系统通知。

JDK 1.2 之后提供了 PhantomReference 来实现弱引用:

Phone phone = new Phone();
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference(phone, queue);

虚引用和弱引用的区别在于:虚引用的使用必须和引用队列联合使用。

也就是说,GC 在回收一个对象时,如果发现对象具有虚引用,那么回收之前就会先将该对象的虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入虚引用来了解被引用的对象是否将要被 GC 回收,如果发现某个虚引用已经被加入引用队列,就可以在所引用对象的内存被回收之前采取必要的处理操作。

参考资料:

《深入理解 Java 虚拟机》

Java 四种对象引用: