目录
- 零、前情概要
- ref包内容
- 类
- 相关线程
- 系列目录
- 上一章回顾
- ref包内容
- 一、why & how
- 为什么要Java引用机制
- Java引用机制的整体流程
- 整体流程
- 流程中需要探索的内容
- 二、Treated specially by GC
- 三、Reference概览
- 整体概览
- 源码中需要关注的点
- 四、referent属性
- 状态变化
- FinalReference的例外
- 状态变化图
- 五、ReferenceQueue和next
- 六、pending-reference list和discovered
- 七、ReferenceHandler
- 总结
零、前情概要
1.java.lang.ref包的内容
- Reference & ReferenceQueue & ReferenceHandler
- SoftReference & WeakReference
- PhantomReference
- jdk.internal.ref.Cleaner
- FinalReference
- Finalizer & FinalizerThread
- java.lang.ref.Cleaner
- # 其中会涉及两个虚拟机线程:ReferenceHandler & FinalizerThread
2.系列目录
- 《ref包简述》
- 《Reference & ReferenceQueue & ReferenceHandler》
- 《SoftReference & WeakReference》
- 《PhantomReference & jdk.internal.ref.Cleaner》
- 《FinalReference & Finalizer & FinalizerThread》
- 《当WeakReference的referent重写了finalize方法时会发生什么》
- 《java.lang.ref.Cleaner》
3.上一章回顾
- 首先通过两个API documentation点出了java.lang.ref包的核心——支持应用与垃圾收集器GC进行有限交互;
- 其次,简单浏览了java.lang.ref包中的四种弱引用(软引用、弱引用、虚引用和FinalReference)和核心逻辑;
- 最后,例举了包规范中的可达性;
一、why & how
1.为什么要Java引用机制
在没有Java引用的场景下,建立相关的资源对象之后,如果在一定的条件下不再需要资源对象(例如内存紧张、发生GC),那你要主动去释放,否则资源对象会一直占用着资源。
// resObj代表资源对象,除了自身分配的Java堆内存资源之外,其分配的可能还有其他资源
// 例如堆外的native memory、文件句柄、socket端口等等
ResourceObject resObj = new ResourceObject("分配资源");
例如强引用,因为建立的资源对象resObj一直都是强可达的,所以无论发生多少次GC,都不会回收resObj。
如果要回收resObj分配的资源:
- Java堆内存:分配给resObj的Java堆内存,你需要断开其强引用使其不可达,才会被GC回收;
- 其他资源:分配给resObj的其他资源例,如堆外的native memory、文件句柄、socket端口等,你也需要手动去释放;
如果忘记释放了呢?那会造成资源泄露。
有没有一种机制能够让你可以提前规避忘记释放资源所造成的风险,或者优雅一点地释放资源?
有,Java引用。
对于Java堆内存,在JVM中是由垃圾收集器GC来进行自动回收(GC在JVM中被称为自动存储管理系统,见《Java虚拟机规范》2.5.3章节)。
无论是Java堆内存,还是应用的其他资源,如果有一种机制能够与JVM GC进行交互联动,使得当GC发生的时候,根据不同的需求能够触发不同的资源回收操作,这个机制就是Java引用期望达到的效果。
前一章提过,支持应用与GC进行交互联动的机制有两个(一个是控制GC的发生,另一个是当GC发生的时候根据不同的需求触发不同的操作):
- 显式GC(explicit garbage collection):即System.gc(),支持在应用中触发GC,一定程度上能控制GC的发生,因为本系列是讲Java引用,所以System.gc相关的参数、不同垃圾收集器下流程等不在本次范围之内;
- Java引用:显式GC能够在一定程度上控制GC的发生,那当GC发生的时候,根据不同的引用类型,GC会触发不同的资源回收操作
- Java堆内存:主要是软引用和弱引用,在《SoftReference & WeakReference》中描述;
- 其他资源:主要是虚引用,在《PhantomReference & jdk.internal.ref.Cleaner》中描述;
- 兜底机制:FinalReference,在《FinalReference & Finalizer & FinalizerThread》中描述;
- java.lang.ref.Cleaner:Java 9提供的资源回收机制,在《java.lang.ref.Cleaner》中描述
2.Java引用机制的整体流程
A.整体流程
- 当JVM中发生垃圾收集的时候,如果GC发现referent是弱可达的(一定要理解上一章可达性的内容和案例,四种弱可达的对象会被垃圾收集器特殊对待“Treated specially by GC”),那么GC会将封装了该referent的java.lang.ref.Reference及其子类的引用对象挂到pending-reference list上,这条链表是由GC维护的;
- 当GC将Reference对象挂到pending-reference list上时,发生一次线程间通信,会通知ReferenceHandler线程来取走这些节点并进行后续处理;
- ReferenceHandler线程拿到节点的后续处理如下:
- 如果节点Reference是jdk.internal.ref.Cleaner的实例,那么调用其方法jdk.internal.ref.Cleaner.clean(),这个方法会执行事先写好的释放资源的代码;
- 否则,如果是其他的引用类型的实例(SoftReference、WeakReference、PhantomReference、FinalReference),那么拿到其实例化时构造函数中传入的ReferenceQueue并调用该队列的enqueue函数,将引用对象入队列;
- 对于SoftReference和WeakReference,入队列其实没有太大意义,因为这俩对应着应用对Java堆内存的两种不同程度的需求,而Java堆内存的释放会由GC自动管理,所以通常使用软引用和弱引用的时候不会关联ReferenceQueue;
- 对于PhantomReference,对应着应用对其他资源的释放需求(例如堆外native memory、文件句柄、socket端口等),几乎没有直接使用PhantomReference的,而都是用其子类jdk.internal.ref.Cleaner,在ReferenceHandler中调用clean方法执行事先写好的释放资源的代码逻辑;
- 对于FinalReference,也几乎没有直接使用该类的,而都是直接使用其子类java.lang.ref.Finalizer,在队列中会发生第二次线程间通信,ReferenceHandler线程在将Reference对象入队列时,会通知FinalizerThread做进一步的处理——即调用FinalReference封装的referent重写的java.lang.Object的finalize()方法;
- 至于java.l