ART学习笔记(四)Reference

ART学习笔记

Posted by Cc1over on December 3, 2019

ART学习笔记(四)Reference

# Reference关键类

Reference是一个抽象模但是其中没有声明abstract方法,但是Reference有四个派生类,分别是SoftReferenceWeakReferencePhantomReferenceFinalizerReference

假如存在一个非Reference对象obj,对GC可能有三种情况:

  • 通过非Reference的引用能搜索到obj,这种情况下,obj不会被回收
  • 通过Reference的引用搜索到obj,这种情况下,obj是否被释放取决于它的那个Reference对象的数据类型以及此次GC的回收策略
  • 无法搜索到的obj,obj会被回收

以ART虚拟机CMS为例:

  • refObj的类型为SoftReference
    • StickyMarkSweep不会释放SoftReference所引用的对象
    • PartialMarkSweepMarkSweep都会释放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对象,如果是则调用ReferenceProcessorDelayReferenceReferent函数
  • 调用ReclaimPhase执行GC回收阶段时,将会调用ReferenceProcessorProcessReferences函数,而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指向的实际对象是否被垃圾回收器标记过(针对不需要处理的SoftReferenceProcessReferences函数中已经进行标记)
  • 如果没有被标记:则将实际对象和ref解绑
  • 最后把ref对象保存到ReferenceProcessorcleared_references中,clear_references保存的是实际对象被视为垃圾的Reference对象

[小结]

总结下来其实在GC过程中对Reference的处理为:

  • InitializePhase初始化的过程中根据GC策略决定是否需要回收SoftReference所引用的实际对象,只有GC回收策略为kGcTypeSticky时才会回收
  • 每当找到一个obj对象之后进行判断,如果是Reference类型,则根据实际的kclass添加到对应的ReferenceQueue中,根据pendingNext串起来构成一个单链表
  • ReclaimPhase执行GC回收阶段的时候如果Reference类型不是FinalizerReference就会根据pendingNext遍历Reference单链表,然后判断是否标记,如果没标记就进行解绑并把这个Reference对象添加到ReferenceProcessorcleared_references中,如果是FinalizerReference则会调用finalizer_reference_queue_.EnqueueFinalizerReferences函数
  • 注意:这里提到的ReferenceQueue是C++层的ReferenceQueue

# Reference & ReferenceQueue

上文的流程总结了WeakReferenceSoftReference在GC过程中造成的影响,但却没有看到ReferenceJava ReferenceQueue的关联?而谜题的答案其实就在ReferenceProcessorcleared_references

[Heap::CollectGarbageInternal]

这个函数的主要工作是:

  • 找到合适的垃圾回收器对象并完成垃圾回收
  • 执行GC后的数据统计,统计情况会影响下次垃圾回收的回收策略
  • 调用reference_proceesorEnqueueClearedReferences函数

在GC结束后,垃圾对象被回收完了,下一步处理的就是ReferenceProcessorcleared_references

[reference_pocessor.cc->ReferencePocessor::EnqeueClearedReferences]

封装一个ClearedReferenceTask并交交由当前线程直接执行

[heap.cc-> ClearedReferenceTask]

调用Java ReferenceQueue的add函数,把ReferenceProcessorcleared_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 ReferenceQueueReference对象添加到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添加到ReferenceProcessorcleared_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方法,把实际对象保存在FinalizerReferencezombie_成员变量中
  • 为了下次GC可以回收这些对象,解除FinalizerReference与实际对象解绑

而在FinalizerReference与实际对象解绑之后,剩下的线索就是ReferenceProcessorcleared_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层FinalizerReferenceadd方法,而就是在这个时候,FinalizerReference对象得到创建,并且绑定它自己的静态变量queue

# 总结

  • SoftReference:不保证每次GC都会回收它们所指向的实际对象,具体到ART虚拟机,回收策略为kGcTypeSticky肯定不会回收
  • WeakReference:每次GC都会回收它们所指向的实际对象
  • PhantomReference:它的功能与回收没有关系,只是提供一种手段告诉使用者某个实际对象被回收了。使用者可以据此做一些清理工作。目的与finalize类似
  • FinalizerReference:专门用于调用垃圾对象的finalize函数,finalize函数调用后,垃圾对象会在下一次GC中被回收
  • 为什么说PhantomReference比finalize函数更优雅,原因为:
    • 虚拟机中只有FinalizerDaemon一个线程调用对象的finalize函数,并且,FinalizerDaemon是虚拟机提供的,开发者没有办法干预它的工作
    • 如果使用PhantomReference的话,开发者就可以根据情况使用多个线程来处理,例如,根据绑定实际对象的类型,通过多个ReferenceQueue并使用多个线程来等待他们并处理实际对象被回收后的清理工作

参考资料:《深入理解Android虚拟机ART》