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 就可以直接带走这些对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

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