ART学习笔记(四)Reference
# Reference关键类
Reference是一个抽象模但是其中没有声明abstract方法,但是Reference有四个派生类,分别是SoftReference、WeakReference、PhantomReference和FinalizerReference
假如存在一个非Reference对象obj,对GC可能有三种情况:
- 通过非Reference的引用能搜索到obj,这种情况下,obj不会被回收
- 通过Reference的引用搜索到obj,这种情况下,obj是否被释放取决于它的那个Reference对象的数据类型以及此次GC的回收策略
- 无法搜索到的obj,obj会被回收
以ART虚拟机CMS为例:
- refObj的类型为SoftReference
- StickyMarkSweep不会释放SoftReference所引用的对象
- PartialMarkSweep与MarkSweep都会释放SoftReference所引用的对象
- refObj的类型为WeakReference
- 不论回收策略是什么,WeakReference所引用的对象都会被释放
- refObj的类型为PhantomReference
- PhantomReference必须配合ReferenceQueue使用
- 不论回收策略是什么,PhantomReference所引用的对象都会被加到ReferenceQueue中,但是PhantomReference所引用的对象不会被回收,只有从ReferenceQueue中取出来,才会在下一次GC过程中被回收
# Reference关键成员变量及函数
public abstract class Reference<T> {
private static boolean slowPathEnable = false;
// referent指向的实际对象
volatile T referent;
// 关联的ReferenceQueue
final ReferentQueue<? super T> queue;
// 多个Reference对象借助queueNext组成一个单向链表
Reference queueNext;
// pendingNext也用于将多个Reference对象组成一个单向链表
// 这个链表的用法主要和GC有关
Reference<?> pendingNext;
// 获取所指向的实际对象
public T get() {
return getReference();
}
private final native T getReferent();
// 解绑和实际对象的关联
public void clear() {
this.referent = null;
}
}
# GC中对Reference的处理
以MarkSweep为例,GC中对Reference进行处理的时机为:
- 调用InitializePhase执行GC初始化阶段时,在这个阶段中设置clear_soft_reference_成员变量,也就是设置是否释放SoftReference所引用对象的标记为,而对CMS而言,只有在kGcTypeSticky也就是StickyMarkSweep的时候,这个标记为会为false,其他情况都为true
- 调用PausePhase函数的过程中,把Reference类的静态变量slowPathEnabled置为true
- 当每找到一个对象,则会进行判断这个obj是否是一个Reference对象,如果是则调用ReferenceProcessor的DelayReferenceReferent函数
- 调用ReclaimPhase执行GC回收阶段时,将会调用ReferenceProcessor的ProcessReferences函数,而MS中clear_soft_reference成员变量也在这个时候作为这个方法的参数而起作用
- ReferenceProcessor是在Heap中创建的,用于Reference的处理
[reference_processor.cc->DelayReferenceReferent]
void ReferenceProcessor::DelayReferenceReferent(mirror::Class* kclass, mirror::Reference* ref, collector::GarbageCollector* collector) {
// 获取Reference所指向的实际对象
mirror::HeapReference<mirror::Object>* referent = ref->GetReferentReferenceAddr();
Thread* self = CurrThread();
if(referent->AsMirrorPtr != nullptr && !collector->IsMarkedHeapReference(referent)) {
// kclass为ref所属的类型
// 以下代码就是根据ref类型kclass加到不同ReferenceQueue中
// ReferenceQueue中的Reference将通过pendingNext构成一个单向链表
if(kclass->IsSoftReferenceClass()) {
soft_reference_queue_.AtomicEnqueueIfNotEnqueued(self,ref);
} else if(kclass->IsWeakReferenceClass()) {
weak_reference_queue_.AtomicEnqueueIfNotEnqueued(self,ref);
} else if(kclass->IsFinalizerReferenceClass()) {
finalizer_reference_queue_.AtomicEnqueueIfNotEnqueued(self,ref);
} else if(kclass->IsPhantomReferenceClass()) {
phantom_reference_queue_.AtomicEnqueueIfNotEnqueued(self,ref);
}
}
}
- 如果这个Referent对象引用的实际对象有没被MarkSweep标记,就会把Reference对象加到对应的ReferenceQueue中,而Reference对象通过pendingNext成员构成一个单向链表
- 如果这个Referent对象引用的实际对象被MarkSweep标记过,说明这个实际对象不需要处理
[process_references.cc->ReferencePrcessor::ProcessReferences]
根据方法的参数clear_soft_reference判断是否需要处理SoftReference,如果为true则调用soft_reference_queue_.ForwardSoftReference方法,这个方法的处理逻辑通过pending_next_遍历弱引用构成的链表,然后把原来这些没有标记的reference对象进行标记
然后会跟根据引用类型做不同的处理:
- SoftReference:调用soft_reference_queue_.ClearWhiteReference函数
- WeakReference:调用weak_reference_queue_.ClearWhiteReference函数
- PhantomReference:调用phantom_reference_queue_.ClearWhiteReference函数
- FinalizerReference:调用finalizer_reference_queue_.EnqueueFinalizerReferences函数
[reference_queue.cc->ReferenceQueue::ClearWhiteReferences]
方法执行流程:
- 从Reference的pending_next_链表中取出一个元素记为ref
- 判断ref指向的实际对象是否被垃圾回收器标记过(针对不需要处理的SoftReference在ProcessReferences函数中已经进行标记)
- 如果没有被标记:则将实际对象和ref解绑
- 最后把ref对象保存到ReferenceProcessor的cleared_references中,clear_references保存的是实际对象被视为垃圾的Reference对象
[小结]
总结下来其实在GC过程中对Reference的处理为:
- 在InitializePhase初始化的过程中根据GC策略决定是否需要回收SoftReference所引用的实际对象,只有GC回收策略为kGcTypeSticky时才会回收
- 每当找到一个obj对象之后进行判断,如果是Reference类型,则根据实际的kclass添加到对应的ReferenceQueue中,根据pendingNext串起来构成一个单链表
- 在ReclaimPhase执行GC回收阶段的时候如果Reference类型不是FinalizerReference就会根据pendingNext遍历Reference单链表,然后判断是否标记,如果没标记就进行解绑并把这个Reference对象添加到ReferenceProcessor的cleared_references中,如果是FinalizerReference则会调用finalizer_reference_queue_.EnqueueFinalizerReferences函数
- 注意:这里提到的ReferenceQueue是C++层的ReferenceQueue
# Reference & ReferenceQueue
上文的流程总结了WeakReference和SoftReference在GC过程中造成的影响,但却没有看到Reference和Java ReferenceQueue的关联?而谜题的答案其实就在ReferenceProcessor的cleared_references中
[Heap::CollectGarbageInternal]
这个函数的主要工作是:
- 找到合适的垃圾回收器对象并完成垃圾回收
- 执行GC后的数据统计,统计情况会影响下次垃圾回收的回收策略
- 调用reference_proceesor的EnqueueClearedReferences函数
在GC结束后,垃圾对象被回收完了,下一步处理的就是ReferenceProcessor的cleared_references
[reference_pocessor.cc->ReferencePocessor::EnqeueClearedReferences]
封装一个ClearedReferenceTask并交交由当前线程直接执行
[heap.cc-> ClearedReferenceTask]
调用Java ReferenceQueue的add函数,把ReferenceProcessor的cleared_references添加进去,明确的是这个Java ReferenceQueue并不是Reference创建的时候绑定的ReferenceQueue
[ReferenceQueue.java-> Reference::add]
static void add(ReferenceQueue<?> list) {
synchronized (ReferenceQueue.class) {
if(unenqeued == null) {
// unenqueued链表还不存在的情况下
unenqeued = list;
}else {
// 把list加到unenqueue pendingNext所在链表中
}
ReferenceQueue.class.notifyAll();
}
}
当Java ReferenceQueue把Reference对象添加到unenqueued链表中后会唤醒一个线程,它唤醒的线程就是ReferenceQueueDeamon线程
[Daemons.java->ReferenceQueueDaemon]
private static class ReferenceQueueDaemon extends Daemon {
public void run() {
while(isRunning()) {
Reference<?> list;
synchronized (ReferenceQueue.class) {
while(ReferenceQueue.unenqueued == null) {
ReferenceQueue.class.wait();
}
list = ReferenceQueue.unenqueued;
ReferenceQueue.unenqueued = null;
ReferenceQueue.enqueuePending(list);
}
}
}
}
[ReferenceQueue-> enqueuePeding]
public static void enqueuePending(Reference<?> list) {
Reference<?> start = list;
do {
ReferenceQueue queue = list.queue;
if (queue == null) {
// list指向一个Reference对象
// 一个Reference对象会关联一个ReferenceQueue
// 取出Reference对象然后把这些Reference对象添加到与之管理的ReferenceQueue中
Reference<?> next = list.pendingNext;
list.pendingNext = list;
list = next;
} else {
synchronized (queue.lock) {
do {
Reference<?> next = list.pendingNext;
list.pendingNext = list;
queue.enqueueLocked(list);
list = next;
} while (list != start && list.queue == queue);
queue.lock.notifyAll();
}
}
} while (list != start);
}
[小结]
在ART中其实整套Reference处理的流程和Hotspot相差甚远,在ART中对Reference添加到ReferenceQueue中的流程为:
C++层:
- 在GC过程中把解绑的Reference添加到ReferenceProcessor的cleared_references中
- 封装一个ClearedReferenceTask并交交由当前线程直接执行
- 调到Java层ReferenceQueue静态方法add
Java层:
- 把C++层的cleareed_reference添加到ReferenceQueue的静态变量unenqueued中,并唤醒ReferenceQueueDeamon线程
- 一个个取出Reference并添加到对应绑定的ReferenceQueue中
- 唤醒等待的线程,比如FinalizerDaemon线程
# FinalizerReference
FinalizerReference比较特殊,是隐藏类,在Java开发中无法使用,主要为虚拟机内部使用,用于调用对象的finalize方法
上文有关FinalizerReference的函数调用链:
- ReclaimPhase
- ReferencePrcessor::ProcessReferences
- ReferenceQueue.EnqueueFinalizerReferences
[reference_queue.cc-> ReferenceQueue::EnqueueFinalizerReference]
函数执行流程:
- 与SoftReference以及WeakReference类似,从finalizer_reference_queue_取出一个Reference对象,记为ref
- 与SoftReference以及WeakReference不同,FinalizerReference将主动标记引用的实际对象,因为所引用的实际对象都是定义了finalize函数的对象,这些对象被回收前要调用它们的finalize函数,因此不能再还没有调用finalize函数之前就回收它们
- 在把实际对象与FinalizerReference中有一个为zombie_的成员变量关联起来
- 解绑ref与实际对象的管理
- 与SoftReference以及WeakReference类似,将ref保存到clear_references中
FinalizerReferences关联的是定义了finalize函数的类的实例,由于必须在垃圾回收前调用它们的finalize方法,所以这次GC就必须主动标记这些对象,避免这些对象在这次GC中被回收
但是下次垃圾回收还是需要释放这些对象,因此EnqueueFinalizerReference函数的目的就是:
- 此次GC主动标记,避免FinalizerReference所引用的对象被回收
- 为了在解绑之后仍可调用这些对象的finalize方法,把实际对象保存在FinalizerReference的zombie_成员变量中
- 为了下次GC可以回收这些对象,解除FinalizerReference与实际对象解绑
而在FinalizerReference与实际对象解绑之后,剩下的线索就是ReferenceProcessor的cleared_references了,对于FinalizerReference来说,其实它也会正常走函数流程:
- Heap::CollectGarbageInternal
- ReferencePocessor::EnqeueClearedReferences
- heap.cc-> ClearedReferenceTask
- ReferenceQueue.java-> Reference::add
- ReferenceQueue-> enqueuePending
而与之不一样的是,在ReferenceQueue-> enqueuePending执行的最后会唤醒等待在Queue上的线程,比如FinalizerDaemon线程
而当与FinalizerReference相关联的ReferenceQueue就是FinalizerReference中的静态变量queue,而当FinalizerDaemon线程被唤醒之后,只要不停地从这个静态变量queue中取出FinalizerReference,然后根据FinalizerReference中提前设置好的zombie成员变量,就可以找到实际对象,然后调用finalize方法
来到这里,其实所有有关finialize函数调用谜题都得到了解决,但是FinalizerReference是什么时候和这个静态变量queue关联起来的呢?
答案其实就是类加载,在类加载过程中如果该类实现了finalize函数,就会给这个kclass标记上一个kAccClassIsFinalizable的标记,而划上这个标记的类在执行Alloc方法的时候就会在C++层通过JNI调用到Java层FinalizerReference的add方法,而就是在这个时候,FinalizerReference对象得到创建,并且绑定它自己的静态变量queue
# 总结
- SoftReference:不保证每次GC都会回收它们所指向的实际对象,具体到ART虚拟机,回收策略为kGcTypeSticky肯定不会回收
- WeakReference:每次GC都会回收它们所指向的实际对象
- PhantomReference:它的功能与回收没有关系,只是提供一种手段告诉使用者某个实际对象被回收了。使用者可以据此做一些清理工作。目的与finalize类似
- FinalizerReference:专门用于调用垃圾对象的finalize函数,finalize函数调用后,垃圾对象会在下一次GC中被回收
- 为什么说PhantomReference比finalize函数更优雅,原因为:
- 虚拟机中只有FinalizerDaemon一个线程调用对象的finalize函数,并且,FinalizerDaemon是虚拟机提供的,开发者没有办法干预它的工作
- 如果使用PhantomReference的话,开发者就可以根据情况使用多个线程来处理,例如,根据绑定实际对象的类型,通过多个ReferenceQueue并使用多个线程来等待他们并处理实际对象被回收后的清理工作
参考资料:《深入理解Android虚拟机ART》