1. 简介
在并发标记结束后,ZGC进入非强引用的并发标记和处理阶段。主要负责处理软引用、弱引用、虚引用、FinalReference(实现了Finalize方法的类的实例)。
2. 引用
2.1 引用的基本概念
引用的类型
memory/referenceType.hpp
enum ReferenceType {
REF_NONE, // 正常的类
REF_OTHER, // java/lang/ref/Reference的子类,但不在下面里面
REF_SOFT, // 软引用,java/lang/ref/SoftReference的子类
REF_WEAK, // 弱引用,java/lang/ref/WeakReference的子类
REF_FINAL, // java/lang/ref/FinalReference的子类
REF_PHANTOM // 虚引用,java/lang/ref/PhantomReference的子类
};
引用主要有4种状态:
- active:活跃
- pending:对象进入pending列表
- enqueued:对象进入引用队列
- Inactive:不活跃,可以回收
pending转变为enqueued由Java线程ReferenceHandler负责,其他由GC线程负责。
2.2 java.lang.ref.Reference
相关引用类别均继承了抽象类java.lang.ref.Reference,Reference主要包含如下成员变量:
// jvm在gc时会将要处理的对象放到这个静态字段上面。
private static Reference<Object> pending = null;
// 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队
// processPendingReferences只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。
// 因为这个pending对象,两个线程都可能访问,因此需要加锁处理。
transient private Reference<T> discovered; /* used by VM */
// 引用中的对象,由GC决定何时、是否回收
private T referent; /* Treated specially by GC */
// 即描述当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义
Reference next;
// 引用的队列,tryHandlePending最后一步就是把Reference对象入队。new的时候如果是null就不入队了。
volatile ReferenceQueue<? super T> queue;
2.3 ReferenceHandler线程
Reference的static代码块中会启动一个线程,该线程会死循环调用processPendingReferences方法,方法
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, null, name, 0, false);
}
public void run() {
while (true) {
// 死循环调用processPendingReferences
processPendingReferences();
}
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
// 最高优先级线程
handler.setPriority(Thread.MAX_PRIORITY);
// 守护线程
handler.setDaemon(true);
handler.start();
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean waitForReferenceProcessing()
throws InterruptedException
{
return Reference.waitForReferenceProcessing();
}
@Override
public void runFinalization() {
Finalizer.runFinalization();
}
});
}
2.4 processPendingReferences
processPendingReferences主要逻辑如下,也比较简单:
private static final Object processPendingLock = new Object();
private static boolean processPendingActive = false;
private static void processPendingReferences() {
// wait直到被notify或JVM通知有pending的引用
waitForReferencePendingList();
Reference<Object> pendingList;
synchronized (processPendingLock) {
pendingList = getAndClearReferencePendingList();
processPendingActive = true;
}
while (pendingList != null) {
// 链表前进一位,并清除链表头的discovered
Reference<Object> ref = pendingList;
pendingList = ref.discovered;
ref.discovered = null;
// 判断是否是jdk.internal.ref.Cleaner
if (ref instanceof Cleaner) {
// 如是,则通常是回收资源的场景,如回收堆外内存
((Cleaner)ref).clean();
synchronized (processPendingLock) {
processPendingLock.notifyAll();
}
} else {
// 否则,就是软、弱、虚引用的处理场景,对象入队
ReferenceQueue<? super Object> q = ref.queue;
if (q != ReferenceQueue.NULL) q.enqueue(ref);
}
}
synchronized (processPendingLock) {
processPendingActive = false;
processPendingLock.notifyAll();
}
}
2.5 软引用的特殊逻辑
SoftReference继承了Reference,但稍微修改下get逻辑:
// 私有静态变量clock,JVM在GC时更新
static private long clock;
// 软引用的纪元,GC根据timestamp和clock判断是否回收
private long timestamp;
public SoftReference(T referent) {
super(referent);
// 创建软引用时,timestamp赋值为clock
this.timestamp = clock;
}
public T get() {
T o = super.get();
// get时,如果timestamp不等于clock,timestamp赋值为clock
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
3. 源码分析
上面章节介绍了Java中对于引用的处理,接下来再介绍下JVM层的处理。
3.1 入口
share/gc/z/zReferenceProcessor.cpp
void ZReferenceProcessor::process_references() {
ZStatTimer timer(ZSubPhaseConcurrentReferencesProcess);
// 多线程并发处理
ZReferenceProcessorTask task(this);
_workers->run_concurrent(&task);
// 更新SoftReference类的私有变量clock
soft_reference_update_clock();
// 更新日志和统计信息
collect_statistics();
}
ZReferenceProcessorTask线程主要逻辑如下:
share/gc/z/zReferenceProcessor.cpp
void ZReferenceProcessor::work() {
// 获取discovered引用链表
oop* const list = _discovered_list.addr();
oop* p = list;
// 遍历整个链表
while (*p != NULL) {
const oop reference = *p;
const ReferenceType type = reference_type(reference);
// 判断该引用是否应该留在_discovered_list
if (should_drop(reference, type)) {
// 从链表移除,reference状态修改为active
*p = drop(reference, type);
} else {
// 保留在链表中,引用置为inactive,前进一位
p = keep(reference, type);
}
}
// 将list追加到pending链表
if (*list != NULL) {
*p = Atomic::xchg(_pending_list.addr(), *list);
if (*p == NULL) {
_pending_list_tail = p;
}
// 清理discovered链表
*list = NULL;
}
}
share/gc/z/zReferenceProcessor.cpp
bool ZReferenceProcessor::should_drop(oop reference, ReferenceType type) const {
const oop referent = reference_referent(reference);
if (referent == NULL) {
// 这种情况表示,应用线程直接调用了Reference.enqueue()或Reference.clear(),从链表移除
return true;
}
// 如果引用还是active,也应该从链表移除
if (type == REF_PHANTOM) {
return ZBarrier::is_alive_barrier_on_phantom_oop(referent);
} else {
return ZBarrier::is_alive_barrier_on_weak_oop(referent);
}
}
3.2 软引用的淘汰策略
软引用的淘汰策略,需要结合2.5节中介绍的软引用特殊逻辑一起看。主要分为两种策略:
- LRUCurrentHeapPolicy,上次GC的释放的MB*常量SoftRefLRUPolicyMSPerMB
share/gc/shared/referencePolicy.cpp
_max_interval = (Universe::heap()->free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
- LRUMaxHeapPolicy,上次GC后空闲的MB*常量SoftRefLRUPolicyMSPerMB
share/gc/shared/referencePolicy.cpp
size_t max_heap = MaxHeapSize;
max_heap -= Universe::heap()->used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
常量SoftRefLRUPolicyMSPerMB指的是每1MB允许软引用存活多少毫秒,默认是1000。
根据策略计算出_max_interval后,释放回收特定引用的算法如下:
share/gc/shared/referencePolicy.cpp
// 获取SoftReference的私有成员变量timestamp的值
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check");
// 如果timestamp小于_max_interval就不回收
if(interval <= _max_interval) {
return false;
}
return true;
4. 总结
本文介绍了引用的一些基本概念,和非强引用回收的基本流程。非强引用回收的过程设计的GC线程和Java线程ReferenceHandler的互动,交互过程非常有意思,值得学习。