欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Reference & ReferenceQueue

程序员文章站 2024-02-11 20:31:34
...

分析了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对象创建的流程如下图:

Reference & ReferenceQueue

Reference & ReferenceQueue

Reference & ReferenceQueue

说明:

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 处理流程如下图:

Reference & ReferenceQueue


说明:

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;