Java中的 强引用、软引用、弱引用、虚引用

缘起

作为想成为高级java工程师的童鞋,JDK提供的四种引用是不得不知道的知识. 因为这些引用类型在框架源码(ThreadLocal中就用到了,参见【1】)中可能会用到. 我们必须要知道这些引用在什么场景下会用到.

分析

首先,我们要讲一下为什么会有四种引用. 有时候一个东西的历史比它本身还重要.

Java 中的强引用的定义很传统:如果强引用类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为力。 我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。 很多系统的缓存功能都符合这样的应用场景。

所以除了强引用之外,Java还规定了软引用、弱引用、虚引用来应对各种应用场景.

强引用(StrongReference)

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

强引用是我们最常用的引用,这个不消多说.

除了强引用之外,其余三种引用都在 java.lang.ref包中可以找到.

软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存(所以软引用并不会造成OOM,这是有效避免OOM的策略)。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(比如网页缓存、图片缓存等 )。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象可达性发生变化的时候(即从可达变得不可达,相关状态见参考【2】),而且符合回收策略(例如内存不足),则GC发生的时候jvm就会把这个软引用加入到与之关联的引用队列中。

应用场景:

像这种如果内存充足, GC 时就保留,内存不够, GC 再来收集的功能很适合用在缓存的引用场景中。在使用缓存时有一个原则,如果缓存中有就从缓存获取,如果没有就从数据库中获取,缓存的存在是为了加快计算速度,如果因为缓存导致了内存不足进而整个程序崩溃,那就得不偿失了。

假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。

设计思路是:用一个HashMap来保存图片的路径字符串path(做key) 和 相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问
题。在Android开发中对于大量图片下载会经常用到。

伪代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.....
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
<br>....
public void addBitmapToCache(String path) {

// 强引用的Bitmap对象

Bitmap bitmap = BitmapFactory.decodeFile(path);

// 软引用的Bitmap对象

SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

// 添加该对象到Map中使其缓存

imageCache.put(path, softBitmap);

}

public Bitmap getBitmapByPath(String path) {

// 从缓存中取软引用的Bitmap对象

SoftReference<Bitmap> softBitmap = imageCache.get(path);

// 判断是否存在软引用,注意,在垃圾回收器对这个Java对象(一块堆内存)回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null(这点对于弱引用也是一样的)。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。

if (softBitmap == null) {

return null; // 如果由于内存不足Bitmap被回收,将取得空

}

// 取出Bitmap对象

Bitmap bitmap = softBitmap.get();

return bitmap;

}

当然这里我们把缓存替换策略交给了JVM去执行(即处理”什么时候内存不足就要回收软引用了”这件事情的策略),这是一种比较简单的处理方法。复杂一点的缓存,我们可以自己单独设计一个类,这里面就涉及到缓存策略的问题了 ,这里面就涉及缓存的常见算法——FIFO、LRU、LFU, 以后我会继续写文章论述并自己实现一个缓存的.

弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存(一个对象有强引用可以阻止被gc的命运,但是如果一个对象只有弱引用的话,是无法阻止该对象被gc的命运的)。不过,由于垃圾回收器是一个优先级很低的线程(除非System.gc()手动敦促GC),因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

1
2
3
4
ps: 一个对象只有软引用软引用其实同样不能阻止垃圾回收器回收该对象,但是可以延迟回收.
这与弱引用中急切回收对象不同。鉴于软引用和弱引用的这一区别,
软引用更适用于缓存机制,而弱引用更适用于存贮元数据(例如弱引用可以用于存储ClassLoader的引用)。
对于软引用和弱引用着的对象,如果存在强引用同时与之关联(强引用没有被去掉),则进行垃圾回收时也不会回收该对象。

应用场景:

ThreadLocal和WeakHashMap, 具体见参考【1】和【3】

虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用(体现在虚引用的构造器只有含有引用队列的构造器,而没有只有引用对象的构造器, 但是软引用、虚引用都有这种构造器,因为虚引用的get方法总是返回null,而软引用和虚引用则不然)。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之后,把这个虚引用加入到与之 关联的引用队列中。

应用场景:

DEMO【1】中的eg5包中的例子就给出了虚引用的一个应用的例子.

这个例子说明了,只有到达了终结阶段的对象(即经过了收集阶段的finalize方法之后还是不可达的对象,或者说没在finalize方法中复活的对象才会将其虚引用对象(如果存在的话)加入到其绑定的队列中去),这里说的”收集阶段、终结阶段”参见参考【2】。

这个例子就说明了虚引用的一个应用场景就是——监视对象的回收. 做法是开启一根后台线程监视虚引用对象绑定的引用队列. 如果被回收了,可以在该daemon线程中打印日志.

小结: 4 种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用。 (强软弱虚),强引用使用最多(平时编程用的大部分都是强引用),其次就是软引用和弱引用,软引用和弱引用,这2种既有相似之处又有区别。软引用和弱引用都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收(当然,前提是都没有强引用关联该对象)。

如果觉得说的还是比较抽象的话,可以看一下DEMO【1】和【2】. 其中 【2】是四大引用的基本用法,【1】涉及了引用绑定队列.

用一张表总结一下Java的四种引用

引用级别 垃圾回收时间 用途 生存时间
强引用 不会被回收 对象的一般状态 JVM停止运行时终止
软引用 在内存不足时 缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 gc运行后终止
虚引用 Unknown Unknown Unknown

参考

【1】https://yfsyfs.github.io/2019/06/27/ThreadLocal-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/

【2】https://yfsyfs.github.io/2019/06/28/%E4%B8%BA%E4%BB%80%E4%B9%88%E5%B0%BD%E9%87%8F%E4%B8%8D%E8%A6%81Override-finalize%E6%96%B9%E6%B3%95/

【3】https://yfsyfs.github.io/2019/06/28/WeakHashMap-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/

DEMO

【1】https://github.com/yfsyfs/backend/tree/master/reference%20in%20java

【2】https://github.com/yfsyfs/backend/tree/master/Reference/src/com/yfs