四种引用类型介绍
- 强引用(Strong Reference) :
默认的对象引用类型,平时通过new等形式创建的对象,都是使用强引用进行关联的。如果一个对象具有强引用,即使JVM经过GC后仍然获取不到足够的内存空间,宁愿抛出OutOfMemoryError也不会回收该对象。
- 软引用(Soft Reference) :
软引用的对象在JVM经过GC后仍然获取不到足够的内存空间时会被回收,回收后内存空间依旧不足才会抛出OutOfMemoryError
- 弱引用(Weak Reference) :
弱引用的对象在JVM下一次GC进行回收,即使内存空间依旧足够。
- 虚引用(Phantom Reference) :
如果一个对象仅持有虚引用,那么它和没有任何引用一样。虚引用不能决定的对象何时被回收,对象何时被回收与自身其他引用情况有关。虚引用与引用队列PhantomReference合用,用于判断虚引用的对象是否被回收,当垃圾回收器准备回收一个具有虚引用的对象时,会在回收前把这个虚引用对象加入到与之关联的引用队列中。
使用方式
强引用
平时的new对象,clone对象,反序列化出来的对象都是强引用对象。
软引用
java
public class SoftReferenceCase {
public static void main(String[] args) {
// 创建一个1G的大对象
byte[] largeByte = new byte[1024*1024*1024];
SoftReference softReference = new SoftReference<>(largeByte);
// 去除强引用,只剩下软引用
largeByte = null;
List<byte[]> list = new ArrayList<>();
while (true){
// 一次创建1G对象
list.add(new byte[1024 * 1024 * 1024]);
// 判断软引用对象是否被回收
if (softReference.get() != null) {
System.out.println("对象未被回收");
} else {
System.out.println("对象已被回收");
}
}
}
}

控制台输出如下,可以看到软对象被回收了,回收后空间依旧不足再抛出OOM 
弱引用
java
public class WeakReferenceCase {
public static void main(String[] args) {
byte[] shortByte = new byte[1];
WeakReference weakReference = new WeakReference<>(shortByte);
// 去除强引用,只剩下弱引用
shortByte = null;
// 判断弱引用对象是否被回收
isRecovery(weakReference);
// 手动gc
System.gc();
// 判断弱引用对象是否被回收
isRecovery(weakReference);
}
private static void isRecovery(WeakReference weakReference) {
if (weakReference.get() != null) {
System.out.println("对象未被回收");
} else {
System.out.println("对象已被回收");
}
}
}

控制台输出如下,可以看到弱引用对象在gc后被回收了,即使内存空间依旧足够。 
虚引用
虚引用使用上需要注意几点:
- 必须和ReferenceQueue配合使用
- PhantomReference的get方法始终返回null
- 当垃圾回收器决定对PhantomReference对象进行回收时,会将其插入ReferenceQueue中。
java
public class PhantomReferenceCase {
public static void main(String[] args) {
String str = new String();
ReferenceQueue queue = new ReferenceQueue<>();
// 虚引用对要跟引用队列一起使用
PhantomReference phantomReference = new PhantomReference<>(str,queue);
//开启子线程,在虚引用对象被回收后进行工作
new Thread(()->{
try {
// 阻塞方法,直到队列中有对象
Reference reference = queue.remove();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("虚引用对象被回收,开始执行清理任务");
}).start();
// 去除强引用,只剩下虚引用
str = null;
// 手动gc,让只剩虚引用的str回收
System.gc();
}
}

控制台输出如下,可以看到虚引用对象在gc后被回收并放进引用队列PhantomReference中了。

一个对象是否能同时存在多种引用?
一个对象可以同时存在强、软、弱、虚引用,对象是否被回收取决于最强的引用类型。 例如,如果一个对象同时拥有强引用和弱引用,那么只要强引用还存在,垃圾回收器就不会回收该对象,即使jvm进行了一次gc,符合弱引用的回收条件,这个对象也不会被回收。
java
public class ReferenceCase {
public static void main(String[] args) throws InterruptedException {
// 创建强引用,largeByte强引用于这个1G的大对象
byte[] largeByte = new byte[1024*1024*1024];
// 创建软引用
SoftReference softReference = new SoftReference<>(largeByte);
// 创建弱引用
WeakReference<Object> weakReference = new WeakReference<>(largeByte);
// 虚引用要跟引用队列一起使用
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 创建虚引用
PhantomReference<Object> phantomReference = new PhantomReference<>(largeByte, referenceQueue);
isRecovery(largeByte, softReference, weakReference, referenceQueue);
System.out.println("手动gc,由于强引用还在,对象不会因为其他引用达到条件而回收");
System.gc();
isRecovery(largeByte, softReference, weakReference, referenceQueue);
// 去除强引用
largeByte = null;
System.out.println("去除强引用");
isRecovery(largeByte, softReference, weakReference, referenceQueue);
System.out.println("手动gc,由于软引用还在,对象不会因为其他引用达到条件而回收");
System.gc();
isRecovery(largeByte, softReference, weakReference, referenceQueue);
List<byte[]> list = new ArrayList<>();
while (true){
// 一次创建1G对象
list.add(new byte[1024 * 1024 * 1024]);
//判断引用情况
System.out.println("创建了一个1G大对象");
isRecovery(largeByte, softReference, weakReference, referenceQueue);
}
}
private static void isRecovery(byte[] largeByte, SoftReference softReference, WeakReference<Object> weakReference, ReferenceQueue<Object> referenceQueue) {
System.out.println("强引用是否存在:"+(largeByte != null));
System.out.println("软引用是否存在:"+(softReference.get() != null));
System.out.println("弱引用是否存在:"+(weakReference.get() != null));
System.out.println("虚引用是否存在:"+(referenceQueue.poll() == null));
}
}

控制台输出如下,在这个例子中,largeByte所指向的对象最初被四种不同的引用所引用。当我们将largeByte设置为null后,该对象已经不存在强引用,largeByte对象的生命周期受余下的软、弱引用类型所影响,余下类型中软引用类型最强,所以该对象只有在内存不足时,才可能被回收,当内存不足时,软引用对象符合条件进行回收,由于又触发了回收,所以弱引用也被回收,由于对象已经不存在其他引用了,largeByte对象能够被真正回收了,同时虚引用对象的引用队列会插入一条数据。

使用场景
软引用
软引用主要用于一些有用但是非必要的对象,比如本地缓存。平时使用本地缓存时,当服务器压力大内存资源不足时会发生OOM问题,缓存由于都是正常new出来的强引用所以并不会被回收,但是缓存中可能存在部分数据是不再需要或者使用频率很低的,完全可以回收掉避免OOM问题的发生,即使回收到需要用的缓存,再重新拉缓存就好了。
以下是Caffeine使用软引用存储value的方式,也支持设置弱引用。 
后续put缓存的时候,会根据value的引用类型创建引用。 
弱引用
常见的ThreadLocal中便使用到了弱引用,避免ThreadLocalMap的key发生内存泄漏问题。以下是threadLocal结构

ThreLocalMap中的entry继承了WeakReference,通过super调用WeakReference的形式对Entry中的key添加一个弱引用。

ThreadLocal的key如果不用弱引用为什么可能导致内存泄漏?
可以看到ThreadLocal的结构中,threadLocal对象跟threadLocalMap中Entry的key存在一条循环引用链,如果key强引用于ThreadLocal对象,当ThreadLocal对象已经完成任务不再使用,由于key还强引用了ThreadLocal对象,所以不能够进行回收,久而久之,threadLocal使用的越多,不再使用了但不能被回收的threadLocal对象就越多,从而导致内存泄漏的问题。
改成弱引用后,当ThreadLocal对象已经完成任务不再使用,此时ThreadLocal对象只剩下threadLocalMap中key的弱引用了,下一次gc时,ThreadLocal对象便会被回收。
虚引用
虚引用通常用于监控对象的回收,并做出一定处理。
如apache io包中的FileCleaningTracker,可以用于在临时文件不再使用时,自动删除

它对临时文件添加虚引用,并有一个守护线程对虚引用对应的队列进行监听,当从队列获取到数据时,会把文件路径放进删除列表中进行删除。 