Reference & ReferenceQueue

分析了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;


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值