「强/软/弱/虚」引用

无论是「引用计数算法」,还是「可达性分析算法」,它们判断对象的存活都离不开「引用」

在 JDK 1.2 之前,Java 里面的引用是很传统的定义:如果 reference 类型的数据中存储的数值代表的是另外一块内存的地址,就称该 reference 数据是代表某块内存、某个对象的引用

这个定义在现在看来过于狭隘,一个对象在这种定义下只有两种状态:「被引用」or「未被引用」

我们希望对于一个对象:当内存空间还足够时,能保留在内存之中;如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这个对象

基于上述所描述的应用场景,在 JDK 1.2 之后,Java 对「引用」的概念进行了扩充,分为四种:强引用、软引用、弱引用、虚引用

注意:上面提到的四种不同强度的引用依旧保持了对对象的引用,无论哪种强度的引用都和未被引用还是有区别的,未被引用的对象在 GC 时一定会被回收!!

强引用 (Strongly Reference) - 不回收

指程序中普遍存在的引用赋值,和传统意义下的引用一致;无论任何情况下,只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象

在 Java 程序中,最常见的引用类型就是强引用 (普通系统 99% 以上都是强引用),是最常见的普通对象引用,也是默认的引用类型

当在 Java 程序中使用new关键字创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用,如:Object obj = new Object()

强引用的对象是可触及的,垃圾收集器永远不会回收掉被引用的对象;而下面三种引用分别对应:软可触及、弱可触及、虚可触及的,在一定条件下都是可以被回收的

所以强引用是造成 Java 内存泄漏和溢出的主要原因!!

软引用 (Soft Reference) - 内存不足即回收

Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand. Soft references are most often used to implement memory-sensitive caches.

指一些还有用,但非必须的对象;只要被软引用关联的对象,在系统将要发生 OOM 前,会对这些对象列进回收范围之中进行第二次回收,如果这次回收后依旧内存不足,才抛出 OOM 异常

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

软引用通常被用来实现内存敏感的缓存,比如:高速缓存就有使用到软引用。在内存不足的时候会清理这些缓存;在内存充足的时候保留这些缓存

再举一个更具体的例子,当使用浏览器访问一个页面,返回后再次访问相同的页面,此时是重新加载该页面,还是在第一次访问时对页面进行缓存,第二次访问直接从缓存中取呢?这涉及到系统的实现:

基于上述场景可以使用软引用,既可以保存访问过的页面内容,也可以在内存不足时回收这些内存,避免内存溢出

场景一:内存充足,手动 GC

如果内存充足的情况下,手动通过System.gc()通知垃圾收集器回收一次垃圾,看看软引用的对象会不会被回收!

输出如下:

分析:软引用的对象并没有被回收,可以通过软引用访问到对象,也没有把软引用加入到关联的引用队列中

结论:在内存充足时,即使手动触发 Full GC,也不会回收软引用的对象!!

场景二:内存不足,自动 GC

如果内存不足的情况下,垃圾收集器会自动触发一次 GC,看看软引用的对象会不会被回收!

输出如下:

分析:软引用的对象被回收了,无法通过软引用访问到对象,同时把软引用加入到关联的引用队列中

结论:在内存不足时,会回收软引用的对象,同时也会把软引用加入到关联的引用队列中 (如果有关联队列的话)

弱引用 (Weak Reference) - 发现即回收

指一些非必须的对象,但它比软引用强度更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生为止

场景一:内存充足,不 GC

输出如下:

分析:虚引用的对象并没有被回收,可以通过虚引用访问到对象,也没有把虚引用加入到关联的引用队列中

结论:在内存充足不触发 GC 的情况下,不会回收虚引用的对象!!

场景二:内存充足,手动 GC

输出如下:

分析:虚引用的对象被回收了,无法通过虚引用访问到对象,同时把虚引用加入到关联的引用队列中

结论:在内存充足手动触发 GC 的情况下,依然会回收虚引用的对象!!

虚引用 (Phantom Reference) - 对象回收跟踪

它是一种最弱的引用关系,虚引用是否存在完全不影响对象的生存时间,也无法通过虚引用获取一个对象的实例,它的作用只是对象在被回收时收到一个系统通知

虚引用必须和引用队列一起使用,虚引用被创建时必须提供一个引用队列作为参数,这一点和软引用和弱引用不同,它们俩是可选,而虚引用是必须!!

当垃圾收集器准备回收一个虚引用对象时,在回收前会把该虚引用加入引用队列中。在对象被回收掉了后,我们可通过引用队列获取被回收的对象引用,这就起到了一个通知的作用

例子一

分析:上面结果证明了无法通过虚引用获取一个对象的实例

例子二

输出如下:

分析:在回收对象前,把对象引用放入了引用队列中