Android 中的引用类型初探

原始地址:Android 中的引用类型初探

引用种类

  • 强引用:在 GC 中如果发现一个对象是可达的,那么 GC 在任何情况都不会回收这个对象

  • 软引用(SoftReference):在 GC 中如果发现一个对象是软可达的 。那么 GC 可以根据内存情况清除这些对象。并且保证在抛出 OutOfMemoryError 异常之前。所有的软引用的对象是已经回收过。

  • 弱引用(WeakReference):在 GC 中如果发现一个对象是软可达的,GC 会回收这些对象。

  • 虚引用(PhantomReference):在 GC 中如果发现是一个幽灵引用的时候,GC 会回收这些对象。

总结: 强引用在任何情况都不会被回收。软引用在 GC 可以被回收。弱引用和虚引用在 GC 中会尽可能回收。

GC 流程简介

Android GC 主要分为 标记 和 清除 阶段、 通过定义两个 Bitmap, Live Bitmap 和 Mark Bitmap , 前者表示上次 GC 存活的对象。后者表示这次 GC 存活的对象。 Mark Bitmap 存在 而 Live Bitmap 不存在的为当前 GC 回收的对象。 GC 结束的时候将 Mark Bitmap 设置为 Live Bitmap。 不管并行还是串行GC, 或者 ART 的 GC 基本流程类似。

Reference 状态。

public abstract class Reference<T> {
    ...
    volatile T referent; 

    final ReferenceQueue<? super T> queue; 

    Reference queueNext;

    Reference<?> pendingNext;
    ...
}

参数介绍:

  • referent:引用对象, referent 回收的时候设置为 null。
  • queue :声明的队列。 不为空的时候,在 referent 被回收以后,最终 Reference 会被添加到队列中去。
  • queueNext :默认为 null, 在 Enqueued 状态表示同一个 queue 下,下一个 Reference 节点。
  • pendingNext:默认为 null ,在 Pending 的时候,表示下一个待处理 Reference 节点

状态装换

Reference 有 4 种状态 Active,Pending,Enqueued,Inactive

image.png

  • queue 不为空:

声明的时候默认为 Active 状态( queueNext 为空 ,pendingNext 为空 )。在 GC 发现 referent 对象可以被回收,回收 referent ,设置 referent 为 null , 将 Reference 放在 clear 队列当中。 状态为 Pending 状态( queueNext 为空 ,pendingNext 不为空 ),GC 会唤醒 ReferenceQueueDaemon 线程处理引用 clear 队列。 ReferenceQueueDaemon 处理 clear 队列。将 Reference 对象放到 queue 队列里面去。 状态为 Enqueued 状态( queueNext 不为空 ,pendingNext 为 Reference )。 当 queue 调用 poll() 将 Reference 获取出来。 状态为 Inactive( queueNext 为 ReferenceQueue.sQueueNextUnenqueued,pendingNext 为 Reference)。

  • queue 为空

声明的时候默认为 Active 状态(queueNext 为空 ,pendingNext 为空 )。在 GC 发现 referent 对象可以被回收,回收 referent ,设置 referent 为 null 。状态为 Inactive (queueNext 为空 ,pendingNext 为空 )。

Reference 处理流程。

虚拟机启动

虚拟机启动的时候会启动守护线程。

public final class Daemons{
   public static void start() {
        ReferenceQueueDaemon.INSTANCE.start(); // 引用队列处理。 
        FinalizerDaemon.INSTANCE.start(); // 处理 finalize 线程
        FinalizerWatchdogDaemon.INSTANCE.start(); // 监听 finalize 方法超时。
        HeapTaskDaemon.INSTANCE.start();
    }
}

加载链接类

在 虚拟机加载和链接类的时候,会对 Class 进行引用类型判断。

 */
enum ClassFlags {
 ...
    CLASS_ISREFERENCE          = (1<<27), // class is a soft/weak/phantom ref
                                          // only ISREFERENCE is set --> soft
    CLASS_ISWEAKREFERENCE      = (1<<26), // class is a weak reference
    CLASS_ISFINALIZERREFERENCE = (1<<25), // class is a finalizer reference
    CLASS_ISPHANTOMREFERENCE   = (1<<24), // class is a phantom reference

 ...
};
static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod,
    Method* meth)
{
    ...
    if (dvmCompareNameDescriptorAndMethod("finalize", "()V", meth) == 0) {

        if (clazz->classLoader != NULL ||
            strcmp(clazz->descriptor, "Ljava/lang/Enum;") != 0)
        {
            SET_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
        }
    }
    ...
}

加载类的时候如果发现自定义了 finalize 方法, 那么会在 class 的 accessFlags 对象打上 CLASS_ISFINALIZABLE 标志。

bool dvmLinkClass(ClassObject* clazz)
{
    ...
 
    if (strcmp(clazz->descriptor, "Ljava/lang/Object;") == 0) {
        /* Don't finalize objects whose classes use the
         * default (empty) Object.finalize().
         */
        CLEAR_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
    } else {
       
        if (IS_CLASS_FLAG_SET(clazz->super, CLASS_ISFINALIZABLE)) {
            SET_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
        }

        /* See if this class descends from java.lang.Reference
         * and set the class flags appropriately.
         */
        if (IS_CLASS_FLAG_SET(clazz->super, CLASS_ISREFERENCE)) {
            u4 superRefFlags;

        
            superRefFlags = GET_CLASS_FLAG_GROUP(clazz->super,
                    CLASS_ISREFERENCE |
                    CLASS_ISWEAKREFERENCE |
                    CLASS_ISFINALIZERREFERENCE |
                    CLASS_ISPHANTOMREFERENCE);
            SET_CLASS_FLAG(clazz, superRefFlags);
        } else if (clazz->classLoader == NULL &&
                clazz->super->classLoader == NULL &&
                strcmp(clazz->super->descriptor,
                       "Ljava/lang/ref/Reference;") == 0)
        {
            u4 refFlags;

            refFlags = CLASS_ISREFERENCE;
            if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/SoftReference;") == 0)
            {
            } else if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/WeakReference;") == 0)
            {
                refFlags |= CLASS_ISWEAKREFERENCE;
            } else if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/FinalizerReference;") == 0)
            {
                refFlags |= CLASS_ISFINALIZERREFERENCE;
            }  else if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/PhantomReference;") == 0)
            {
                refFlags |= CLASS_ISPHANTOMREFERENCE;
            } else {
                /* No-one else is allowed to inherit directly
                 * from Reference.
                 */
//xxx is this the right exception?  better than an assertion.
                dvmThrowLinkageError("illegal inheritance from Reference");
                goto bail;
            }

            SET_CLASS_FLAG(clazz, refFlags);
        }
    }
   ...
    return okay;
}

链接类的时候:
当一个 Class 是 SoftReference 或者它的派生类则它的 accessFlags 会被设置为 CLASS_ISREFERENCE
当一个 Class 是 WeakReference 或者 它的派生类则它的 accessFlags 被设置为CLASS_ISREFERENCE | CLASS_ISPHANTOMREFERENCE
当一个 Class 是 PhantomReference 或者 它的派生类则它的 accessFlags 被设置为 CLASS_ISREFERENCE | CLASS_ISPHANTOMREFERENCE
当一个 Class 是 FinalizerReference 则它的 accessFlags 被设置为 CLASS_ISREFERENCE | CLASS_ISFINALIZERREFERENCE。 它没有派生类, 因为 FinalizerReference 是 Final 。
当一个 Class 拥有自定义的 finalize()方法, 或者父类拥有finalize()方法, 那么就会被打上 CLASS_ISFINALIZABLE 标识。 这里有一个是例外 。Objectfinalize()是一个空实现。 它又是所有类的父类。 它会被清除 CLASS_ISFINALIZABLE 标识。因为如果不这样, 所有的类都将被打上 CLASS_ISFINALIZABLE

对象初始化。

/* File: c/OP_INVOKE_OBJECT_INIT_RANGE.cpp */
HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT_RANGE /*{vCCCC..v(CCCC+AA-1)}, meth@BBBB*/)
    {
     ...
         */
        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
            EXPORT_PC();
            dvmSetFinalizable(obj);
            if (dvmGetException(self))
                GOTO_exceptionThrown();
        }

     ...
        FINISH(3);
    }
OP_END

在类初始化的时候, 会根据 class 是否有 CLASS_ISFINALIZABLE, 即 拥有 自定义 finalize 方法。 那么会调用 dvmSetFinalizabledvmSetFinalizable 内部调用了 Java 的 FinalizerReference.add 方法。

public final class FinalizerReference<T> extends Reference<T>{
    // This queue contains those objects eligible for finalization.
    public static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();

    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;
        }
    }
}

这里生成了一个新的引用 FinalizerReference 来持有对象,所有的FinalizerReference 设置同一个 queue 。同时将所有的 FinalizerReference 串联起来。

GC

标记动作就是从 根集 对象开始标记,在标记对象的时候,会根据对象的引用类型,添加到对应的引用队列中。

*
 * Process the "referent" field in a java.lang.ref.Reference.  If the
 * referent has not yet been marked, put it on the appropriate list in
 * the gcHeap for later processing.
 */
static void delayReferenceReferent(Object *obj, GcMarkContext *ctx)
{
   ...
       if (pending == NULL && referent != NULL && !isMarked(referent, ctx)) {
        Object **list = NULL;
        if (isSoftReference(obj)) {
            list = &gcHeap->softReferences;
        } else if (isWeakReference(obj)) {
            list = &gcHeap->weakReferences;
        } else if (isFinalizerReference(obj)) {
            list = &gcHeap->finalizerReferences;
        } else if (isPhantomReference(obj)) {
            list = &gcHeap->phantomReferences;
        }
        assert(list != NULL);
        enqueuePendingReference(obj, list);
    }
}

接下来处理 4 种引用队列。 软引用, 弱引用, 虚引用, finalizer 引用队列

void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
                              Object **weakReferences,
                              Object **finalizerReferences,
                              Object **phantomReferences)
{
 ...
    /*
     * Unless we are in the zygote or required to clear soft
     * references with white references, preserve some white
     * referents.
     */
    if (!gDvm.zygote && !clearSoftRefs) {
        preserveSomeSoftReferences(softReferences);
    }
    /*
     * Clear all remaining soft and weak references with white
     * referents.
     */
    clearWhiteReferences(softReferences);
    clearWhiteReferences(weakReferences);
    /*
     * Preserve all white objects with finalize methods and schedule
     * them for finalization.
     */
    enqueueFinalizerReferences(finalizerReferences);
    /*
     * Clear all f-reachable soft and weak references with white
     * referents.
     */
    clearWhiteReferences(softReferences);
    clearWhiteReferences(weakReferences);
    /*
     * Clear all phantom references with white referents.
     */
    clearWhiteReferences(phantomReferences);
    /*
     * At this point all reference lists should be empty.
     */
...
}
static void clearWhiteReferences(Object **list)
{
    GcMarkContext *ctx = &gDvm.gcHeap->markContext;
    size_t referentOffset = gDvm.offJavaLangRefReference_referent;
    while (*list != NULL) {
        Object *ref = dequeuePendingReference(list);
        Object *referent = dvmGetFieldObject(ref, referentOffset);
        if (referent != NULL && !isMarked(referent, ctx)) {
            /* Referent is white, clear it. */
            clearReference(ref);
            if (isEnqueuable(ref)) {
                enqueueReference(ref);
            }
        }
    }
}
static void enqueueReference(Object *ref)
{
    assert(ref != NULL);
    assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queue) != NULL);
    assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queueNext) == NULL);
    enqueuePendingReference(ref, &gDvm.gcHeap->clearedReferences);
}

对于弱引用,虚引用, 如果他们没有被标记,那么他们所持有的对象将会回收,referent 设置为 null 。 而他们本身根据 queue 是否为空进入不同状态, 为空将进入Inactive 状态。 不为空 进入Pending 状态。 所有的引用类型会被添加到 Clear 队列中。 此时加入的队列并不是他们自己的 queue 。
对软引用来说. 并不会全部回收, 默认情况会回收一半。除非是即将发生 OOM 才会全部回收。这也是软引用和 弱引用,虚引用的主要区别。
对于 FinalizerReferences 队列来说的话, 需要调用 enqueueFinalizerReferences 方法

static void enqueueFinalizerReferences(Object **list)
{
    GcMarkContext *ctx = &gDvm.gcHeap->markContext;
    size_t referentOffset = gDvm.offJavaLangRefReference_referent;
    size_t zombieOffset = gDvm.offJavaLangRefFinalizerReference_zombie;
    bool hasEnqueued = false;
    while (*list != NULL) {
        Object *ref = dequeuePendingReference(list);
        Object *referent = dvmGetFieldObject(ref, referentOffset);
        if (referent != NULL && !isMarked(referent, ctx)) {
            markObject(referent, ctx);
            dvmSetFieldObject(ref, zombieOffset, referent);
            clearReference(ref);
            enqueueReference(ref);
            hasEnqueued = true;
        }
    }
    if (hasEnqueued) {
        processMarkStack(ctx);
    }
}

由于还需要执行 finalizer 方法。 所以需要讲还没执行过 finalizer 方法并且未标记的对象标记,防止执行 finalizer 方法前对象被销毁了。然后将 Reference 添加到 Clear 队列。

注: 将 Reference 加入自身的 queue 方法 和 finalizer 方法均不在 GC 过程中调用。因为 GC 时间是宝贵的。

处理后续 Clear 队列 交给了守护线程 ReferenceQueueDaemon 。


    private static class ReferenceQueueDaemon extends Daemon {
        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();

        ReferenceQueueDaemon() {
            super("ReferenceQueueDaemon");
        }

        @Override public void runInternal() {
            while (isRunning()) {
                Reference<?> list;
                try {
                    synchronized (ReferenceQueue.class) {
                        while (ReferenceQueue.unenqueued == null) {
                            ReferenceQueue.class.wait();
                        }
                        list = ReferenceQueue.unenqueued; // 
                        ReferenceQueue.unenqueued = null;
                    }
                } catch (InterruptedException e) {
                    continue;
                } catch (OutOfMemoryError e) {
                    continue;
                }
                // 添加到自己的 queue
                ReferenceQueue.enqueuePending(list);
            }
        }
    }

ReferenceQueue.unenqueued 就是 Clear 队列。将引用添加到自己的 queue 里面。 状态由 Pending 变更为 Enqueued

对于 FinalizerReference 对象的 finalize 方法。 它的处理交给 FinalizerDaemon

 private static class FinalizerDaemon extends Daemon {

        private final ReferenceQueue<Object> queue = FinalizerReference.queue;
        @Override public void runInternal() {

            while (isRunning()) {
                try {
                    ...
                    FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
                    ...
                    doFinalize(finalizingReference);
                } catch (InterruptedException ignored) {
                } catch (OutOfMemoryError ignored) {
                }
            }
        }
        private void doFinalize(FinalizerReference<?> reference) {
            FinalizerReference.remove(reference);
            Object object = reference.get();
            reference.clear();
            try {
                object.finalize();
            } catch (Throwable ex) {    
            } finally {
                finalizingObject = null;
            }
        }
    }

它是处理是从 FinalizerReference 的 queue 获取 FinalizerReference。 这里 queue 里面存的 FinalizerReference 已经是Enqueued 说明它持有的对象,已经应该需要销毁了。 所有获取的对象然后调用他们的 finalize 方法, 同时拦截所有的异常。并且不做处理。直接结束。 下次的 GC 就可以直接带走这些对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、四大引用级别的概念 强引用:就是正常的引用,类似于下面:Object object = new Object(...
    漠简尘阅读 2,740评论 0 2
  • JDK1.2之后,Java扩充了引用的概念,将引用分为强引用、软引用、弱引用和虚引用四种。 强引用类似于”Obje...
    lesline阅读 4,979评论 0 0
  • ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 ...
    tomas家的小拨浪鼓阅读 36,852评论 10 59
  • 感知GC。怎么感知:* 通过get来判断已经被GC(PhantomReference 在任何时候get都是null...
    YDDMAX_Y阅读 1,979评论 0 4
  • 我的事情请让我自己做主——妈妈 今年不找男朋友,也不会去相亲,我知道您关心我,但您要知道您的女儿已经成年了,已经有...
    默然1阅读 185评论 0 0

友情链接更多精彩内容