Reference & ReferenceQueue

来源:互联网 发布:微传单制作软件 编辑:程序博客网 时间:2024/04/28 11:22

分析了Java FinalizerReference的创建,Finalizer的执行,以及GC时 Reference的处理。

1.Finalizable Class

public class Test {    public void finalize() {        close();    }}

1.在 LoadClass => LoadMethod时,发现函数名是 "finalize",返回值是 void,
   会把这个class 设置 finalizable:klass→SetFinalizable();
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;    }}

  1. queue 是 FinalizerReference 的静态成员,是一个 ReferenceQueue
  2. 从其成员可以看出来,FinalizerReference 是一个链表节点,可以用来实现链表结构
  3. 其中 head 是一个链表头
  4. 参数 referent 就对应着 Finalizable的类的对象,它是从 ART 中传递过来
  5. zombie 在 GC 时使用,当referent对应的 Finalizable 对象不被引用时,将其赋值给 zombie,并置 referent为 null
  6. 在这里会对 referent 创建一个其对应的 FinalizerReference对象,并将 queue关联到该对象上
  7. 然后把这个 FinalizerReference 对象插入到链表头,作为 head
  8. 在 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();}

  1. 这个线程会从 FinalizerReference 的静态成员 ReferenceQueue queue 中取出需要执行 finalize 的 Reference;
  2. 然后转换为 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;    }}
  1. 在大多数情况下 FinalizerReference.queue 中没有数据,FinalizerDaemon 线程处于循环 wait() 状态
  2. 在 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):

    1. 这个函数的功能就是把 cleared_references_ 记录的所有类型的 Reference 对象构成的  list
      添加到   ReferenceQueue静态成员 static Reference<?> unenqueued 当中;
    2. 加入完成后,会通过 ReferenceQueue.class.notifyAll(); 来通知 ReferenceQueueDaemon 线程,
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;