Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是如下的情况:
Object a=new Object();
obj持有的Object对象的引用就是强引用,在Java中并没有对应的Reference类。
本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。
Reference是java中的引用类,它用来给普通对象进行包装,当JVM在GC时,按照引用类型的不同,在回收时执行不同的逻辑。先来看下这个类的继承体系:
由图可知,Java存在以下几种引用:
Java中的引用有:
- 强引用(StrongReference):强引用就是我们平时创建对象,创建数组时的引用。强引用在任何时候都不会被GC回收掉;
- 软引用(SoftReference):软引用是在系统发生OOM之前才被JVM回收掉。软引用常被用来对于内存敏感的缓存;
- 弱引用(WeakReference):一旦JVM执行GC,弱引用就会被回收掉;
- 虚引用(PhantomReference):虚引用主要作为其指向referent被回收时的一种通知机制;
- FinalReference:用于收尾机制(finalization) 。
引用实例的几个状态
-
Active:当处于Active状态,GC会特殊处理引用实例,一旦GC检测到其可达性发生变化,GC就会更改其状态。此时分两种情况,如果该引用实例创建时有注册引用队列,则会进入Pending状态,否则会进入Inactive状态。新创建的引用实例为Active。
-
Pending:当前为pending列表中的一个元素,等待被ReferenceHandler线程消费并加入其注册的引用队列。如果该引用实例未注册引用队列,则永远不会处于这个状态。
-
Enqueued:该引用实例创建时有注册引用队列并且当前处于入队列状态,属于该引用队列中的一个元素。当该引用实例从其注册引用队列中移除后其状态变为Inactive。如果该引用实例未注册引用队列,则永远不会处于这个状态。
-
Inactive:当处于Inactive状态,无需任何处理,一旦变成Inactive状态则其状态永远不会再发生改变。整体迁移流程图如下:
整体迁移流程图如下:
如上的状态是为了更好的理解而虚拟出来的状态,并没有一个字段来描述状态,而是通过queue和next字段来标记的。
- Active:当实例注册了引用队列,则queue = ReferenceQueue;当实例没有注册引用队列,那么queue = ReferenceQueue.NULL。next = null;
- Pending:处在这个状态下的实例肯定注册了引用队列,queue = ReferenceQueue。next = this;
- Enqueued:处在这个状态下的实例肯定注册了引用队列,queue = ReferenceQueue.ENQUEUED,next指向下一个在此队列中的元素,或者如果队列中只有当前对象时为当前对象this;
- Inactive:queue = ReferenceQueue.NULL;next = this。
1、Reference
我们先看下 Reference类及重要属性的定义如下:
public abstract class Reference{ //引用的对象 private T referent; //回收队列,由使用者在Reference的构造函数中指定 volatile ReferenceQueue super T> queue; //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构 volatile Reference next; //在GC时,HotSpot底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由HotSpot设置 transient private Reference discovered; //进行线程同步的锁对象 static private class Lock { } private static Lock lock = new Lock(); //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue private static Reference
注意Reference指的是引用对象,而Referent指的是所指对象。
一个Reference对象的生命周期如下:
HotSpot在GC时将需要被回收的Reference对象加入到DiscoveredList中,然后将DiscoveredList的元素移动到PendingList中。PendingList的队首元素由Reference类中的pending属性持有。
2、ReferenceHandler
ReferenceHandler的代码实现如下:
private static class ReferenceHandler extends Thread { ... public void run() { while (true) { tryHandlePending(true); } } } static boolean tryHandlePending(boolean waitForNotify) { Reference
源源不断的从PendingList中获取元素,然后加入到ReferenceQueue中,开发者可以通过调用ReferenceQueue的poll()方法来感知对象被回收的事件。
另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean()方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用,后面会详细介绍。
3、ReferenceQueue
ReferenceQueue是引用队列,垃圾收集器在检测到适当的可达性更改后将已注册的引用对象追加到该队列。
public class ReferenceQueue{ public ReferenceQueue() { } // 内部类Null类继承自ReferenceQueue,覆盖了enqueue方法返回false private static class Null extends ReferenceQueue
从源码上看,实际上ReferenceQueue
只是名义上的引用队列,它只保存了Reference
链表的头(head)节点,并且提供了出队、入队等操作,而Reference
实际上本身提供单向链表的功能,也就是Reference
通过属性next构建单向链表,而链表的操作通过ReferenceQueue这个类来
完成。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
2、调试HotSpot源代码
3、HotSpot项目结构
4、HotSpot的启动过程
5、HotSpot二分模型(1)
6、HotSpot的类模型(2)
7、HotSpot的类模型(3)
8、HotSpot的类模型(4)
9、HotSpot的对象模型(5)
10、HotSpot的对象模型(6)
11、操作句柄Handle(7)
12、句柄Handle的释放(8)
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
26、字段解析之OopMapBlock(4)
27、方法解析之Method与ConstMethod介绍
28、方法解析
29、klassVtable与klassItable类的介绍
30、计算vtable的大小
31、计算itable的大小
32、解析Class文件之创建InstanceKlass对象
33、字段解析之字段注入
34、类的连接
35、类的连接之验证
36、类的连接之重写(1)
37、类的连接之重写(2)
38、方法的连接
39、初始化vtable
40、初始化itable
41、类的初始化
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!