Java--Java中的四种引用
Java中存在四种引用,StrongReference(强引用) 、SoftReferenc(软引用) 、WeakReferenc(弱引用)、PhantomReference(虚引用).虽然不常用,但是对于理解Java的回收等级还是很有帮助的,一句话来说这些引用只是不同回收等级的一种表现形式.
StrongReference(强引用)
强引用是最经常使用的一种引用,如new操作创建的对象就属于强引用.如下代码,对于强引用要记住无论如何JVM都不会去回收其内存.
清单1:强引用示例
1 | Object obj = new Object(); |
SoftReferenc(软引用)
软引用是由java.lang.ref.SoftReference
所提供的功能,被其所关联的对象不存在强引用并且此时JVM内存不足才会去回收该对象.
个人不知道其用处,做缓存的话,现在的企业项目基本不是单体架构所以用处不大,倒是可以做内存警告,当对象被回收时则说明系统所需要的内存不足,那么就可以发邮件通知相关人员.
WeakReferenc(弱引用)
弱引用是java.lang.ref包下的WeakReference类所提供的包装功能,对于弱引用JVM会在GC时回收弱引用所关联的对象。也就是说弱引用对象在不存在强引用关系下,会在一次gc之后被回收,如下代码,其中obj1
没被回收,因为其的引用是强引用,但是weakObj1
与其关联是弱引用,因此不属于被收回对象。weakObj2
所关联的new Object()
只有一个弱引用关联,因此会被回收。
清单2:弱引用示例
1 | Object obj1 = new Object(); |
WeakHashMap应用实例
Java中提供了一个很棒的工具类WeakHashMap
,按照注释所说,该类是一个键为弱引用类型的Map,与传统Map不同的是其键会自动删除释放掉,因为gc()时会自动释放,因此很适合做缓存这一类的需求,下面代码是Tomcat所实现的LRU(最少使用策略)缓存算法的实现,关键点在注释中给出.
清单3:弱引用实现LRU
1 | import java.util.Map; |
此方法本质是借助GC来判断哪些对象还在被强引用,因为被强引用的对象不会被回收,因此WeakHashMap
中key不为null的便认为是仍有效的,放入到eden区中。
PhantomReference(虚引用)
虚引用是由java.lang.ref.PhantomReference
所提供的关联功能,虚引用只是一个通知标记,对其原对象的生命周期毫无影响,当其所引用对象被回收时其会自动加入到引用队列中。也就是说你可以通过虚引用得到哪些对象已被回收。具体用法可以分析common.io
中的org.apache.commons.io.FileCleaningTracker
,该类使用需引用标记对象,当对象被回收后,可以删除对应的文件信息。该类中有一内部类class Tracker extends PhantomReference<Object>
,也就是其包裹着虚引用对象,分析其构造函数,marker
参数是该具体的虚引用,当marker被回收时,该对应的Track会被加入到引用队列queue
中。
清单4:虚引用示例
1 | Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue<? super Object> queue) { |
文件删除则是该类维护的一个线程来进行的操作,既然对象回收后会加入到引用队列queue
,那么该线程要做的功能自然是从引用队列中获取到对应的Track
,然后执行其删除策略。在这个流程中虚引用起到的是跟踪所包裹对象作用,当包裹的的对象被回收时,这边会得到一个通知(将其加入到引用队列)。
清单5:虚引用回调
1 |
|
ReferenceQueue
无论是弱引用还是虚引用,其一个目地是在于即使拥有对象引用,也能被GC,另一个目地则是应用中可以得到对象被GC的通知,ReferenceQueue
则是用来实现该通知的媒介,ReferenceQueue
是单链表引用队列,是GC和应用系统交互的一种方式,Reference
对象是单链表中的节点类,当对象被回收时GC会将回收信息加入到ReferenceQueue
中,应用系统能够拿到回收信息,近而做资源释放等处理,比如WeakHashMap
,那具体是怎么实现的呢?
在WeakHashMap
构造过程中每一个K-V会被封装成java.util.WeakHashMap.Entry
对象,该对象继承了WeakReference
,从构造函数来看每一个Key实际上为WeakReference
包裹的对象。
清单6:WeakHashMap的Entry构造函数
1 | Entry(Object key, V value, |
从构造函数可以得出Key是弱引用,而Value仍然是强引用,因此当Key被回收时,WeakHashMap
要做对应的Value清理工作,否则由于value无法回收,则可能出现内存泄漏现象。此时问题可以简化为两个,一是对象被回收时,WeakHashMap
是如何得到通知的,二则是WeakHashMap
是如何做清理的。
先看问题一,如何感知对象被GC?由于Entry
的结构,因此每一个Key是弱引用,当被回收后,GC会将对应Entry加入到引用队列中,其作为引用对象时数据结构如下所示,为单链表节点。
清单7:Reference数据结构
1 | public abstract class Reference<T> { |
Reference
有个内部类java.lang.ref.Reference.ReferenceHandler
,其继承了Thread类,会在类加载阶段创建一个高优先级守护线程,如下图所示,通过Debug可以很容易发现该线程。GC在完成回收时,会把被回收对象加入到ReferenceQueue
中,然后该线程会去扫描ReferenceQueue
队列,获取被回收对象后,执行自定义清理方法,这样完成了整个通知流程。
第二个问题,WeakHashMap
是如何清理的?WeakHashMap
主要在所有public方法中都调用了expungeStaleEntries
进行主动清理,该方法会扫描引用队列,发现对象后,则将对应的value置为null,从而协助GC。从这里来看,如果声明WeakHashMap后不再访问,实际上还是会有内存泄漏风险,而并不是自动回收不会出现泄漏。
清单7:WeakHashMap对象清理
1 | private void expungeStaleEntries() { |
参考文章
理解Java中的弱引用
Java Reference核心原理分析
- 版权声明: 感谢您的阅读,本文由屈定's Blog版权所有。如若转载,请注明出处。
- 文章标题: Java--Java中的四种引用
- 文章链接: https://mrdear.cn/posts/java_reference.html