标签:hot method href 句柄 直接 handle weakref 作者 rem
Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是如下的情况:
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 pending = null;
}
注意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 r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 如果是Cleaner对象,则记录下来,下面做特殊处理
c = r instanceof Cleaner ? (Cleaner) r : null;
// 指向PendingList的下一个对象
pending = r.discovered;
r.discovered = null;
} else {
// 如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
}
...
// 如果时Cleaner对象,则调用clean方法进行资源回收
if (c != null) {
c.clean();
return true;
}
// 将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
ReferenceQueue super Object> q = r.queue;
if (q != ReferenceQueue.NULL)
q.enqueue(r);
return true;
}
源源不断的从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 {
boolean enqueue(Reference> r) {
return false;
}
}
// ReferenceQueue.NULL和ReferenceQueue.ENQUEUED都是内部类Null的新实例
static final ReferenceQueue NULL = new Null();
static final ReferenceQueue ENQUEUED = new Null();
// 静态内部类,作为锁对象
private static class Lock { };
private final Lock lock = new Lock();
// 引用链表的头节点
private volatile Reference extends T> head;
// 引用队列长度,入队则增加1,出队则减少1
private long queueLength = 0;
// 入队操作,只会被Reference实例调用
boolean enqueue(Reference extends T> r) {
// 加锁
synchronized (lock) {
// Check that since getting the lock this reference hasn‘t already been
// enqueued (and even then removed)
// 如果引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED,则入队失败返回false
ReferenceQueue> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// Self-loop end, so if a FinalReference it remains inactive.
// 如果链表没有元素,则此引用实例直接作为头节点,否则把前一个引用实例作为下一个节点
r.next = (head == null) ? r : head;
// 当前实例更新为头节点,也就是每一个新入队的引用实例都是作为头节点,已有的引用实例会作为后继节点
head = r;
// 队列长度增加1
queueLength++;
// Update r.queue *after* adding to list, to avoid race
// with concurrent enqueued checks and fast-path poll().
// Volatiles ensure ordering.
// 当前引用实例已经入队,那么它本身持有的引用队列实例置为ReferenceQueue.ENQUEUED
r.queue = ENQUEUED;
// 特殊处理FinalReference,VM进行计数
if (r instanceof FinalReference) {
VM.addFinalRefCount(1);
}
// 唤醒所有等待的线程
lock.notifyAll();
return true;
}
}
// 引用队列的poll操作,此方法必须在加锁情况下调用
private Reference extends T> reallyPoll() {
Reference extends T> r = head;
if (r != null) {
r.queue = NULL;
// Update r.queue *before* removing from list, to avoid
// race with concurrent enqueued checks and fast-path
// poll(). Volatiles ensure ordering.
@SuppressWarnings("unchecked")
Reference extends T> rn = r.next;
// Handle self-looped next as end of list designator.
// 更新next节点为头节点,如果next节点为自身,那么队列中只有当前这个对象一个元素
head = (rn == r) ? null : rn;
// Self-loop next rather than setting to null, so if a
// FinalReference it remains inactive.
// 当前头节点变更为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题
r.next = r;
// 队列长度减少1
queueLength--;
// 特殊处理FinalReference,VM进行计数
if (r instanceof FinalReference) {
VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
// 队列的公有poll操作,主要是加锁后调用reallyPoll
public Reference extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
// ...
}
从源码上看,实际上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源码剖析系列文章!
Java引用类型
标签:hot method href 句柄 直接 handle weakref 作者 rem
原文地址:https://www.cnblogs.com/mazhimazhi/p/13502748.html