理解 Java 软引用、弱引用、虚引用及其使用场景

一、 概述

几乎所有允许动态分配内存的计算机语言都会遇见一个共同问题——如何“收集”不在使用的内存。

有点和餐厅类似,开始时,餐厅的所有餐桌均处于空闲状态,可供顾客使用。但是,当所有餐桌都已经被分配给客户时,就需要检查哪些已经被分配的餐桌处于空闲状态。

有些语言,比如 C 语言,把这个责任交给用户:分配到了内存,那么你就有责任释放内存。这就和快餐很像,用完餐后,需要把餐桌整理干净。如果所有顾客都这样正确处理,这种方式效率很高,但是,如果有顾客忘记清理餐桌,就会出现问题。内存空间也一样,很容易忘记释放已经分配的内存。

垃圾收集器(GC)就用来解决此类问题。在 Java 中,GC 使用特定算法“收集”不在使用的内存,程序员不需要手动释放。

假设 Java GC 完美运行,并且它能释放所有不再可达的对象。那么,就会引入一个新问题——想保留对一个对象的引用,但是当该对象没有其余引用时,又不想阻止 GC 对其回收。就像在餐厅用完餐后还想坐一会,但是当有新顾客需要这张餐桌时,可以立即离开。

顺便在这里说一下,我目前是在职Java开发,如果在学习Java的过程当中有遇见任何关于学习方法,学习路线等方面的问题,你都可以点击加入 Java技术讨论组,这里面聚集了很多正在学习Java技术的初学者,也有不少从事Java开发岗的大佬,与Java相关的问题都可以随时发出来讨论。文件夹整理了最新的Java基础精讲视频教程以及我做Java技术这段时间整理的一些学习手册。

二、Java 四种引用

为了解决这类问题,Java 在原来强引用基础上新引入了三类应用——软引用(SoftReference),弱引用(WeakReference)和虚引用(PhantomReference)。看下 Java Doc 如何介绍:

软引用 SoftReference,由 GC 根据内存需求自行清除软引用对象。 软引用最常用于实现内存敏感缓存。在 JVM 抛出 OOM 之前,保证对所有软引用可达对象都已被回收。否则,对 SoftReference 引用对象回收时间以及不同 SoftReference 引用对象回收顺序没有限制;

弱引用 WeakReference,弱引用不会阻止其引用对象标记为可终结状态、被终结以及被回收。 弱引用最常用于实现规范化映射。假设,GC 在某个时间点确定某个对象只有弱引用可达,届时,它自动地将所有指向该对象的所有弱引用清除,并且,如果该对象通过弱引用或强引用链所引用的对象也变成只有弱引用可达,GC也会将这些将指向这些对象的弱引用清除。同时,GC 会将刚才形成的只有弱引用可达对象标记为可终结状态。同时或者稍后,GC 会将刚才清除的弱引用加入到创建时注册的引用队列中。

虚引用 PhantomReference,如果 GC 在某个时间点确定虚引用的所指对象只有虚引用可达,届时或稍后将其加入引用队列。为了确保可回收对象可继续使用,无法通过虚引用获取其引用对应,即虚引用的 get 方法始终返回 null。和软引用以及虚引用不同,在其加入引用队列时,不会自动被 GC 清除。通过虚引用可达对象始终是可达的,直到所有虚引用被清除或者本身变得不可达。

简而言之:软引用尝试保留其引用对象,弱引用不会试图保留其引用对象,虚引用所引用对象不会被被释放直到所有指向该对象的虚引用被清除。

再次以餐厅比喻:SoftReference 就像一位顾客说,“只有当没有其他餐桌可用时,我才会离开我的餐桌。” WeakReference 就像一个顾客在新顾客到来时准备离开。 PhantomReference 就像一个顾客准备在新顾客到来时立即离开,但实际上直到经理允许他才离开。

三、SoftReference 软引用

1. 真的全部回收吗?

官方Java Doc指出,在 JVM 抛出 OOM 之前,保证对所有软引用可达对象都已被回收。

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

真的是这样吗?

在 java 1.2 中首次引入软引用时确实如此,但是从 java 1.3.1 开始引入了 jvm 参数 -XX:SoftRefLRUPolicyMSPerMB(默认为10ms),如果该值设置为0,那么Java Doc中这句话就没问题了。

软引用对象能够被回收需要满足一定的逻辑判断,判断公式如下:

clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB

1

clock表示上次 GC 的时间戳,timestamp 表示最近一次读取软引用对象的时间戳,这两者的差值表示该软引用对象多久没被使用了,差值越大,软引用对象价值越低,负数则表示软引用对象刚刚被使用。freespace 是空闲空间大小,SoftRefLRUPolicyMSPerMB 表示每一 MB 的空闲内存空间可以允许软引用对象存活多久。

如果SoftRefLRUPolicyMSPerMB 取默认值1 ms,这意味着如果只有 10MB 可用堆内存,GC 将释放已使用超过 10 秒的引用。

2. 应用场景

正如Java Doc 说明, 软引用最常用于实现内存敏感缓存,当堆内存不足时,释放缓存空间。

这里分享Spring 缓存配置属性缓存,配置属性在应用启动时读取,运行过程中很有可能不会再次使用,所以Spring 大佬使用软引用缓存。

三、WeakReference 弱引用

GC 一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都将其会回。但是注意Java Doc指出,GC 会将清除的弱引用加入到创建时注册的引用队列中。

例如,Student 类包含 String 类型属性 name,和 int 类型属性 age,如下程序

 

GC 进行垃圾回收时,sr (hashcode 为 1973336893)只有弱引用可达,GC 会将其清除,并加入到创建 sr 时注册的引用队列stuQueue,在垃圾回收结束后可以通过stuQueue获取到该软引用。

同时sr 所引用的 name 属性也只有软引用 name(hashcode 为 1212899836),GC也会将其清除,并加入到创建 sr 时注册的引用队列strQueue中。

弱引用有哪些应用场景呢?

考虑如下场景:

在开发中,可能会遇到将一个类使用修饰符 final 将其标识为不可拓展。或者一个对象是通过静态工厂方法返回的一个接口。这时,这个类不能被继承,所以不能添加新功能。那如果想存储与之关联的额外信息怎么办呢?

例如,有一个用 final 修饰的类 Product, 现在想记录该类每个对象的序列号。但是由于 Product 类并不包含这个属性,而且也不能扩展导致我们也不能增加这个属性。其实一点问题也没有,HashMap完全可以解决上述的问题。

serialNumberMap.put(product, productSerialNumber);

1

这表面看上去没有问题,但是 serialNumberMap 对 product 对象的强引用很有可能会引发问题。当一个 product 不需要时,我们应该将这个条目从 map 中移除。如果我们没有移除的话,可能会导致内存泄露。

一种解决方案是使用消息通知机制——product 销毁时,通知 serialNumberMap 删除。

另外一种比较高效的解决方案是使用弱引用。

可以考虑将 serialNumberMap 键封装在弱引用 WeakReference中。当 map 中某个product 只有弱引用可达是,“弱键”用并不会阻止 GC 将其回收,并在GC回收该“弱键”时,这个“弱键”也同时会被添加到引用队列中。当下一次需要操作 map 时,删除 map 中被已经被回收了的“弱键”(存在引用中)所对应的键值对。

这些已经被java.util.WeakHashMap封装好了,可以直接拿来用,但是需要知道的是,其线程不安全。

四、PhantomReference 虚引用

注意Java Doc对虚引用的说明——如果 GC 在某个时间点确定虚引用的所指对象只有虚引用可达,届时或稍后将其加入引用队列。和软引用以及虚引用不同,在其加入引用队列时,不会自动被 GC 清除。

虚引用所引用对象不会被被释放直到所有指向该对象的虚引用被清除,所以, 虚引用通常用来跟踪对象被垃圾回收的活动。

比如,虚引用可用于在某些对象超出范围以执行某些资源清理时通知您。

Object.finalize() 方法不能保证在对象生命周期结束时被调用,所以如果需要关闭文件或释放资源,你可以依赖虚引用。

由于虚引用没有指向实际对象的链接,因此典型的方式是从 PhantomReference 继承出自己的引用类型,并添加一些对资源释放用的信息,比如文件名。

Java编程学习交流圈子,603835449【点击进入】
分享(源码、项目实战视频、项目笔记,基础入门教程)
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值