Reference & ReferenceQueue
分析了Java FinalizerReference的创建,Finalizer的执行,以及GC时 Reference的处理。
1.Finalizable Class
public class Test {
public void finalize() {
close();
}
}
2.在 LinkSuperClass 时,发现 super class 是 finalizable,则设置这个类为 finalizable;
3.一般 finalize() 函数用来做一些对 Native 对象的析构清理动作;
2.FinalizerReference 对象的创建
每个 FinalizerReference 对象保存着一个 Finalizable 对象,以便在 FinalizerDaemon 线程中访问执行 Finalizable 对象的
finalize 函数。当一个 Finalizable 的类创建对象时,其对应的 FinalizerReference 同时被创建,并添加在
FinalizerReference 的 list(head)中:
// FinalizerReference.java, FinalizerReference 的几个成员 public static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); private static FinalizerReference<?> head = null; private FinalizerReference<?> prev; private FinalizerReference<?> next; private T zombie; public static void add(Object referent) { FinalizerReference<?> reference = new FinalizerReference<Object>(referent, queue); synchronized (LIST_LOCK) { reference.prev = null; reference.next = head; if (head != null) { head.prev = reference; } head = reference; } }
- queue 是 FinalizerReference 的静态成员,是一个 ReferenceQueue
- 从其成员可以看出来,FinalizerReference 是一个链表节点,可以用来实现链表结构
- 其中 head 是一个链表头
- 参数 referent 就对应着 Finalizable的类的对象,它是从 ART 中传递过来
- zombie 在 GC 时使用,当referent对应的 Finalizable 对象不被引用时,将其赋值给 zombie,并置 referent为 null
- 在这里会对 referent 创建一个其对应的 FinalizerReference对象,并将 queue关联到该对象上
- 然后把这个 FinalizerReference 对象插入到链表头,作为 head
- 在 FinalizerReference对象 doFinalize()的时候,会通过如下调用,把 FinalizerReference对象从这列表中删除
FinalizerReference.remove(reference);
Object object = reference.get(); //获取的是zombie成员,此时其指向 referent
reference.clear(); // zombie = null;
object.finalize();
FinalizerReference 继承自 Reference,对象创建时,调用 super(referent, queue):
volatile T referent; /* Treated specially by GC */ final ReferenceQueue<? super T> queue; Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = queue; }
所以这个 queue 和 FinalizerReference中的 queue是同一个对象;
到这里,我们只是给 Finalizable对象创建了一个 FinalizerReference,并将其插入到 FinalizerReference对应的
一个静态链表的头部;
此时没有把其放到 ReferenceQueue 中,所以从 FinalizerReference.queue 是拿不到这个 FinalizerReference 的;
由于这个直接引用,导致 referent 也不会被回收;
FinalizerReference对象创建的流程如下图:
说明:
1.Class Test有 void finalize() 函数,所以它是一个 Finalizable 的类,当另外一个 class Hello 的函数 Fun1() new 一个 Test 对象时,ART在分配一个 Test 对象 test 时,还会通过一个调用 FinalizerReference.add(test),为这个对象创建一个 FinalizerReference,并添加到 FinalizerReference.head 链表中;这个表只是为了引用 FinalizerReference,防止其被回收,无法进行 Finalize 工作,在后面 FinalizerDaemon 线程,对一个 FinalizerReference 的成员 zombie对象(在当前这个阶段,还保存在 referent成员里),做完 Finalize工作后,就会把这个 FinalizerReference 从FinalizerReference.head 链表冲删除;2.new FinalizerReference 时,queue:使用的是 FinalizerReference.queue 传递给父类 Reference,所以每个 FinalizerReference 对象使用的queue都是 FinalizerReference.queue;referent:使用的是 test 对象,所以 FinalizerReference 对象对应的父类成员 referent 指向 test 对象;3.FinalizerReference 类的 get() 函数返回的是 zombie,Reference 类的 get() 函数返回的是 referent;
3.Finalize 执行时机
Daemon.java 中启动的四个线程中的一个线程专门用来做 Finalize动作:
public static void start() { ReferenceQueueDaemon.INSTANCE.start(); FinalizerDaemon.INSTANCE.start(); FinalizerWatchdogDaemon.INSTANCE.start(); HeapTaskDaemon.INSTANCE.start(); }
- 这个线程会从 FinalizerReference 的静态成员 ReferenceQueue queue 中取出需要执行 finalize 的 Reference;
- 然后转换为 FinalizerReference,获取其对应的 Finalizable 对象,执行其 finalize 函数:
FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll(); doFinalize(finalizingReference); private void doFinalize(FinalizerReference<?> reference) { FinalizerReference.remove(reference); Object object = reference.get(); reference.clear(); try { object.finalize(); } catch (Throwable ex) { // The RI silently swallows these, but Android has always logged. System.logE("Uncaught exception thrown by finalizer", ex); } finally { // Done finalizing, stop holding the object as live. finalizingObject = null; } }
- 在大多数情况下 FinalizerReference.queue 中没有数据,FinalizerDaemon 线程处于循环 wait() 状态
- 在 FinalizerReference.queue 被填充后,打破循环,进行 finalize 工作, 这个queue的填充,在下面几节来说明
4. GC MarkingPhase,enqueue FinalizerReference
Finalize 相当于 C++的析构函数,代表在一个 java 对象被回收之前,给这个对象一次机会,干点自己想干的事情。
既然涉及到回收,必然就是通过 GC来实现的,在这里我们简单说明一下 GC 过程中 Reference处理,以便理解整个流程。
在GC的 Mark 阶段,一个已经被标记的 object,在遍历其成员时,会先标记其对应的 klass对象
(object-inl.h Object::VisitReferencce函数);
1.如果其是一个正常的 class对象,则依次标记它的成员对象;2.如果这个对象是一个 Reference 类型的对象(下面表里的几种Reference对象,都属于Reference对象),则会使用DelayReferenceReferentVisitor 来处理这个object,实际就是用 void ReferenceProcessor::DelayReferenceReferent()这个函数来处理这个对象;3.从 DelayReferenceReferent 函数实现可以看到,对于 Reference对象,我们并不会去标记其对应的 referent 对象,在发现 referent 不为空,且此时没有被标记,即没有被这个 Reference 对象之外的其他非 Reference 对象引用(所以Reference的处理要放在GC最后阶段),我们会把 Reference 对象放到各类 Reference对应的ReferenceQueue 中等待处理;
static constexpr uint32_t kClassFlagReference = kClassFlagSoftReference | kClassFlagWeakReference | kClassFlagFinalizerReference | kClassFlagPhantomReference; void ReferenceProcessor::DelayReferenceReferent(mirror::Class* klass, mirror::Reference* ref, collector::GarbageCollector* collector) { mirror::HeapReference<mirror::Object>* referent = ref->GetReferentReferenceAddr(); if (referent->AsMirrorPtr() != nullptr && !collector->IsMarkedHeapReference(referent)) { Thread* self = Thread::Current(); if (klass->IsSoftReferenceClass()) { soft_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); } else if (klass->IsWeakReferenceClass()) { weak_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); } else if (klass->IsFinalizerReferenceClass()) { finalizer_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); } else if (klass->IsPhantomReferenceClass()) { phantom_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); } else { LOG(FATAL) << "Invalid reference type " << PrettyClass(klass) << " " << std::hex << klass->GetAccessFlags(); } } } // Reference queues used by the GC. ReferenceQueue soft_reference_queue_; ReferenceQueue weak_reference_queue_; ReferenceQueue finalizer_reference_queue_; ReferenceQueue phantom_reference_queue_; ReferenceQueue cleared_references_;
这里的 Reference 则对应 Java层的Reference;
从下面代码可以知道 AtomicEnqueueIfNotEnqueued 这个函数把 Reference对象添加,
相当于把这个 Reference插入到 Native层 ReferenceQueue 对应的一个 Reference 列表 list_头部;
void ReferenceQueue::AtomicEnqueueIfNotEnqueued(Thread* self, mirror::Reference* ref) { EnqueueReference(ref); } void ReferenceQueue::EnqueueReference(mirror::Reference* ref) { if (IsEmpty()) { list_ = ref; } else { mirror::Reference* head = list_->GetPendingNext(); ref->SetPendingNext(head); } list_->SetPendingNext(ref); }
- 至此,已经把需要处理的各类 Reference 正确填充到他们各自对应的 ReferenceQueue 中了。
5.GC ReclaimPhase,ProcessReferences
在上面 Mark 过程中,实际上填充的都是虚拟机内部的数据集合,还没有把这些需要被回收的 referent 对象
的 Reference 给放到 java层;
下面要说明的就是如何把数据传递给 java层:
在回收阶段,我们通过 ReferenceProcessor 的 ProcessReferences()函数,处理前面 Mark阶段收集的各类 Reference。
在这里,我们关注一下 FinalizerReference是怎么处理的:
ProcessReference(...) { finalizer_reference_queue_.EnqueueFinalizerReferences(&cleared_references_, collector); collector->ProcessMarkStack(); }
1.主要在 EnqueueFinalizerReferences 函数中,从上一步的 finalizer_reference_queue_ 的 list_的 head 依次取出Reference对象2.处理在 Mark 阶段产生的所有 Reference对象,其referent不为空,且没有被标记,
2.1 会先Mark这个 referent,防止其被回收,因为后面需要使用这个对象来执行其 finalize 函数;2.2 把这个 FinalizerReference 对象的 zombie 成员设置为 referent 对象地址,并清空 referent 成员;2.3 把这个 Reference 放到 cleared_references_ 对应的 ReferenceQueue 中;
其他类型的 Reference的处理,是直接把 referent 置为 null,然后把 Reference 填充到 cleared_references_中。
之后会专门处理 cleared_references_,通过调用 Java 层的 ReferenceQueue.add() 函数,把 cleared_references_ 中
记录的 mirror::Reference* list_; 添加到 ReferenceQueue静态成员 static Reference<?> unenqueued 当中,
用来给 ReferenceQueueDaemon来使用;
也就是说:我们想一下,如果一个 Reference 的 referent 被标记了,说明这个 referent 还在被使用,我们就不能处理这个 Reference ,
否则其他直接使用 referent 的地方就出错了;
需要执行 finalize 的 FinalizerReference 都在 cleared_references_ 的 list里了,所以 finalize 对象的获取肯定是
从这个list中过来的。
接下来是 ReferenceQueue 类的静态函数 static void add(Reference<?> list):
- 这个函数的功能就是把 cleared_references_ 记录的所有类型的 Reference 对象构成的 list
添加到 ReferenceQueue静态成员 static Reference<?> unenqueued 当中; - 加入完成后,会通过 ReferenceQueue.class.notifyAll(); 来通知 ReferenceQueueDaemon 线程,
- 这个函数的功能就是把 cleared_references_ 记录的所有类型的 Reference 对象构成的 list
class ReferenceQueueDaemon:
@Override public void run() {
while (isRunning()) {
Reference<?> list;
try {
synchronized (ReferenceQueue.class) {
while (ReferenceQueue.unenqueued == null) {
ReferenceQueue.class.wait();//一般情况,ReferenceQueueDaemon线程在这循环,当收到上面的notify后,跳出循环
}
list = ReferenceQueue.unenqueued; //把 ReferenceQueue 的 unenqueued数据都拿过来
ReferenceQueue.unenqueued = null;
}
} catch (InterruptedException e) {
continue;
} catch (OutOfMemoryError e) {
continue;
}
ReferenceQueue.enqueuePending(list); // 根据Reference的成员 queue的不同,分别放到不同的ReferenceQueue中
}
}
ReferenceQueueDaemon线程唤醒后:
1.直接把 ReferenceQueue 的 unenqueued数据都拿过来放到一个list,并将其清空;2.调用 ReferenceQueue.enqueuePending(list); 把这个list中的Reference对象根据 其不同的queue,进行 Reference.queue.enqueueLocked(Reference) 这个操作;也即 queque相同的Reference都通过这个操作放到一起去了,不同的 Reference显然应该有不同的 queque;3.对应FinalizerReference,我们从上面知道他们所有的对象对应的父类Reference的成员 queque,都是共用的其静态成员 FinalizerReference.queue;4.所以queue.enqueueLocked(Reference) 对应 FinalizerReference 时,会把 FinalizerReference对象添加到其成员 head 对应的Reference 队列的末尾;5.此时 FinalizerReference.queue里面已经有数据;6.在这个queue里没有数据时,FinalizerDaemon线程一直在 ReferenceQueue.remove(0)里循环等待;7.所以FinalizerReference.queue填充后,ReferenceQueue.remove(0)函数通过 reallyPollLocked()从 head 队列拿到Reference,从而返回,后面FinalizerDaemon线程开始正常工作了,直至此次queue里的数据 finalize完成,又开始在 ReferenceQueue.remove(0)里循环等待;
class FinalizerDaemon:
@Override public void run() {
int localProgressCounter = progressCounter.get();
while (isRunning()) {
try {
FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
if (finalizingReference != null) {
finalizingObject = finalizingReference.get();
progressCounter.lazySet(++localProgressCounter);
} else {
finalizingObject = null;
progressCounter.lazySet(++localProgressCounter);
// Slow path; block.
FinalizerWatchdogDaemon.INSTANCE.goToSleep();
//一般情况下,FinalizerDaemon线程是在阻塞在这里,FinalizerWatchdogDaemon在sleep
finalizingReference = (FinalizerReference<?>)queue.remove();
finalizingObject = finalizingReference.get();
progressCounter.set(++localProgressCounter);
FinalizerWatchdogDaemon.INSTANCE.wakeUp();
}
doFinalize(finalizingReference);
} catch (InterruptedException ignored) {
} catch (OutOfMemoryError ignored) {
}
}
上面描述的 Reference 处理流程如下图:
说明:
1.主要流程就是从一次 GC 唤醒的 ReferenceDaemon 线程,然后唤醒了 FinalizerDaemon 线程,然后 FinalizerDaemon线程进行 doFinalize()工作;2.具体实现代码主要在 reference_queue.cc,reference_processor.cc,ReferenceQueue.java,Daemons.java,FinalizerReference.java;
补充:
1.FinalizerReference之外的其他类型的Reference,一般在APP中自己进行 new创建对象,
并提供一个自己的 ReferenceQueue,当 Reference包含的对象referent 被回收后,会把
该Reference 放到提供的queue中,用以通知,也可以不提供,相当于并不想及时的关注
Reference 的回收;
2.对于 FinalizerReference,一般是在 Finalizable对象创建的时候,虚拟机自行创建的,
用以实现 Finalize;
3.FinalizerReference的ReferenceQueue 静态成员 queue是 public访问的,也就是说其
有可能被误用(等待验证);
4.我们自己提供一个 ReferenceQueue来创建 FinalizerReference,用以实现自己的Finalize;
上一篇: php数组的一些常见操作汇总
下一篇: 使用Java操作MongoDB
推荐阅读
-
Reference & ReferenceQueue
-
Pycharm中无法导入包问题:Unresolved reference 解决方法
-
The projects in the reactor contain a cyclic reference
-
java-Reference
-
Conda - Command reference
-
undefined reference to 'pthread_create'问题解决
-
Reference 博客分类: java.lang.ref Reference
-
Reference 博客分类: java.lang.ref Reference
-
ReferenceQueue 博客分类: java.lang.ref ReferenceQueue
-
ReferenceQueue 博客分类: java.lang.ref ReferenceQueue