四种引用类型介绍
- 强引用(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
,可以用于在临时文件不再使用时,自动删除
它对临时文件添加虚引用,并有一个守护线程对虚引用对应的队列进行监听,当从队列获取到数据时,会把文件路径放进删除列表中进行删除。