Java的强、软、弱、虚引用

四种引用类型介绍

  1. 强引用(Strong Reference)

默认的对象引用类型,平时通过new等形式创建的对象,都是使用强引用进行关联的。如果一个对象具有强引用,即使JVM经过GC后仍然获取不到足够的内存空间,宁愿抛出OutOfMemoryError也不会回收该对象。

  1. 软引用(Soft Reference)

软引用的对象在JVM经过GC后仍然获取不到足够的内存空间时会被回收,回收后内存空间依旧不足才会抛出OutOfMemoryError

  1. 弱引用(Weak Reference)

弱引用的对象在JVM下一次GC进行回收,即使内存空间依旧足够。

  1. 虚引用(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后被回收了,即使内存空间依旧足够。

虚引用

虚引用使用上需要注意几点:

  1. 必须和ReferenceQueue配合使用
  2. PhantomReference的get方法始终返回null
  3. 当垃圾回收器决定对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中便使用到了弱引用,避免ThreadLocalMapkey发生内存泄漏问题。以下是threadLocal结构

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

ThreadLocal的key如果不用弱引用为什么可能导致内存泄漏?

可以看到ThreadLocal的结构中,threadLocal对象跟threadLocalMapEntrykey存在一条循环引用链,如果key强引用于ThreadLocal对象,当ThreadLocal对象已经完成任务不再使用,由于key还强引用了ThreadLocal对象,所以不能够进行回收,久而久之,threadLocal使用的越多,不再使用了但不能被回收的threadLocal对象就越多,从而导致内存泄漏的问题。

改成弱引用后,当ThreadLocal对象已经完成任务不再使用,此时ThreadLocal对象只剩下threadLocalMapkey的弱引用了,下一次gc时,ThreadLocal对象便会被回收。

虚引用

虚引用通常用于监控对象的回收,并做出一定处理。

apache io包中的FileCleaningTracker,可以用于在临时文件不再使用时,自动删除

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

相关推荐
FF在路上22 分钟前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进28 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人1 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.1 小时前
Mybatis-Plus
java·开发语言
不良人天码星1 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1701 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云1 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络1 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。2 小时前
Docker学习
java·开发语言·学习
如若1232 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python