android AMS的回收内存功能

如果你不知道AMS代表的是ActivityManagerService的话,请按ctrl(command)+W,或按鼠标回退键。因为你接着往下看可能会觉得反胃,恶心,眩晕,虽然我好像写的挺易懂的样子,HOHOHOHOHO~

通常android中谈论到GC的时候,我们更喜欢往Java的GC上面去靠,然后套路性的谈论GC的方法,GC的发起时间,如果可以靠到JVM,甚至是Dalvik,ART的GC的策略应该已经可以把*装的蛮好了。

今天我们就来谈论在AMS在内存回收中起到的作用。

我们通常的理解中AMS做的工作更多的是管理Activity,去做Activity的线程启动,分配Activity的资源,慢点!你说什么!分配Activity的资源!分配完了你不负责回收么,既然是你分配的,你不回收??

打开你的AMS类,不要怕,即使有300+L的import,20000+L的代码量也不用担心,我们就看几个东西,不疼的,就像被蚊子扎了一下。。

从闲置Idle开始

首先我们定位到这样一个方法,名字叫activityIdle。字面意思,activity的闲置方法,你想既然activity闲置了,是不是就是说明我们回收内存的机会来了。
我们先不急着看,这个方法做了什么,我们希望追根溯源的去找一下,这个方法在什么地方被调用的,所谓知其然,知其所以然。

如果你读过我的binder分析大法,那么当你对着这个方法Find Usage发现没有什么反映了之后你应该不会慌乱的想到要从ActivityManagerNative出发了。
如果你这边有所疑问,你可以先去看一下
突然悟出在源码中分析Binder的玩法

最终我们找到在如下的地方调用到了这个activityIdle方法

private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            ...
            if (a != null) {
                mNewActivities = null;
                IActivityManager am = ActivityManagerNative.getDefault();
                ActivityClientRecord prev;
                do {
                    if (localLOGV) Slog.v(
                        TAG, "Reporting idle of " + a +
                        " finished=" +
                        (a.activity != null && a.activity.mFinished));
                    if (a.activity != null && !a.activity.mFinished) {
                        try {
                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
                            a.createdConfig = null;
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                    prev = a;
                    a = a.nextIdle;
                    prev.nextIdle = null;
                } while (a != null);
            }
            ...
        }
}

这是Message队列的内部类闲置handler,这个queueIdle回调在消息队列中没有消息可以处理的空闲时期被调起,此处不做深入,知道即可。那我们看一下,这个Idler在哪里被调用了。

找到在handleResumeActivity中,

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            ...
            //如果是其他activity可以跳转过来的
            if (!r.onlyLocalRequest) {
                r.nextIdle = mNewActivities;
                mNewActivities = r;
                if (localLOGV) Slog.v(
                    TAG, "Scheduling idle handler for " + r);
                Looper.myQueue().addIdleHandler(new Idler());
            }
            ...
        } else {
            ...
        }
}

也就是说,在activity执行resume方法之后,系统会在当前的线程中添加一个空闲任务。

这边需要科普一下,一般我们在调用了finish方法,或者是启动了一个新的应用或者是activity方法之后,当前的activity会处于后台,并且处于空闲,因此就会触发queueIdle的方法,从而触发AMS的activityIdle的方法。

然后我们正式的打开activityIdle方法

public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
        final long origId = Binder.clearCallingIdentity();
        synchronized (this) {
            ActivityStack stack = ActivityRecord.getStackLocked(token);
            if (stack != null) {
                ActivityRecord r =
                        mStackSupervisor.activityIdleInternalLocked(token, false, config);
                if (stopProfiling) {
                    if ((mProfileProc == r.app) && (mProfileFd != null)) {
                        try {
                            mProfileFd.close();
                        } catch (IOException e) {
                        }
                        clearProfilerLocked();
                    }
                }
            }
        }
        Binder.restoreCallingIdentity(origId);
}

主要就是检测当前activity栈是否为空,如果栈中有activity,那么就调用ActivityStactSupervisor.activityIdleInternalLocked方法

方法比较长,其实出于本文的主旨考虑,可以只留一个trimApplications方法,但是为了大家可以有一些其他的收获,这边把涉及到activity生命周期的部分方法做了保存并注解,有心的读者可以发散开来,以对onStop,onDestory有更深入的了解。

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
            Configuration config) {
        if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);

        ArrayList<ActivityRecord> finishes = null;
        ArrayList<UserState> startingUsers = null;
        int NS = 0;
        int NF = 0;
        boolean booting = false;
        boolean activityRemoved = false;
        ...

        if (allResumedActivitiesIdle()) {
            if (r != null) {
                mService.scheduleAppGcsLocked();
            }

            ...
        }

        // 获取已经暂停的activity列表
        final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(true);
        NS = stops != null ? stops.size() : 0;
        if ((NF = mFinishingActivities.size()) > 0) {
            // 获取已经触发了finish方法的列表
            finishes = new ArrayList<>(mFinishingActivities);
            mFinishingActivities.clear();
        }

        if (mStartingUsers.size() > 0) {
            startingUsers = new ArrayList<>(mStartingUsers);
            mStartingUsers.clear();
        }

        for (int i = 0; i < NS; i++) {
            r = stops.get(i);
            final ActivityStack stack = r.task.stack;
            if (stack != null) {
                //如果该被暂停的activity已经调用了finish方法,那么就调用栈的finish当前的activity的方法
                if (r.finishing) {
                    stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
                } else {
                    // 否则调用栈的stopActivity方法
                    stack.stopActivityLocked(r);
                }
            }
        }

        // 遍历finish列表中的每一个activity,如果当前栈不为空,就去触发栈的destroyActivityLocked方法
        for (int i = 0; i < NF; i++) {
            r = finishes.get(i);
            final ActivityStack stack = r.task.stack;
            if (stack != null) {
                activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
            }
        }

        ...

        mService.trimApplications();
        //dump();
        //mWindowManager.dump();

        if (activityRemoved) {
            resumeFocusedStackTopActivityLocked();
        }

        return r;
    }

主要的回收方法是trimApplications方法,这个方法倒是真的一点都无法删减了

final void trimApplications() {
        synchronized (this) {
            int i;
            //遍历mRemovedProcesses,杀死所有的进程
            for (i=mRemovedProcesses.size()-1; i>=0; i--) {
                final ProcessRecord app = mRemovedProcesses.get(i);
                //如果此进程没有activity,且没有广播,且没有服务方可被kill掉
                if (app.activities.size() == 0
                        && app.curReceiver == null && app.services.size() == 0) {
                    Slog.i(
                        TAG, "Exiting empty application process "
                        + app.toShortString() + " ("
                        + (app.thread != null ? app.thread.asBinder() : null)
                        + ")\n");
                    if (app.pid > 0 && app.pid != MY_PID) {
                        app.kill("empty", false);
                    } else {
                        try {
                            app.thread.scheduleExit();
                        } catch (Exception e) {
                            // Ignore exceptions.
                        }
                    }
                    cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
                    mRemovedProcesses.remove(i);

                    if (app.persistent) {
                        addAppLocked(app.info, false, null /* ABI override */);
                    }
                }
            }
            updateOomAdjLocked();
        }
    }

直接把mRemovedProcesses的所有进程统统杀死,mRemovedProcesses中保存的是crash的进程,ANR的进程,以及调用killbackprocess等的进程。

略为复杂的updateOomAdjLocked方法

调用updateOomAdjLocked方法,更新所有进程的oom adj,以及在必要的时候kill掉一些进程。并且根据内存等级回调触发onTrimMemory的方法,具体onTrimMemory具体做什么是需要我们自己去实现的。所以真正起到回收内存作用的还是更新oom adj和必要时候kill进程。

方法代码极长,如果大家感兴趣,可以去看这位大哥写的方法分析
Android 7.0 ActivityManagerService(8) 进程管理相关流程分析(2)updateOomAdjLocked

最终就是这个方法让AMS在内存回收中起到了重要的作用。简要的来讲,总共做了如下一些工作:

  • 根据当前进程列表情况,将后台进程中adj数值较高的和空进程分配到待回收槽中,槽的大小有限,一旦超过了槽的大小,那么就会对槽中的进程根据LRU情况,kill掉最久没使用的进程
  • 不断的计算进程列表中各进程的adj值,并进行更新。
  • 计算进程的回收等级,根据回收等级触发onTrimMemory的回调

Linux内核搭配所做的内存回收工作

如果你熟悉Linux内核的LMK机制,那你应该能知道,LowMemoryKiller会在适当的时候对进程进行杀死处理。这里就是利用了adj进行评判每个进程的重要等级,然后在内存吃紧的时候杀死优先级低的进程并发送Kill Signal。

内核还有一个oom killer,在LMK依然解决不了问题的时候,系统为新的应用程序分配内存的时候会出现OOM的错误,这个时候就轮到oom killer出场了,它会对计算进程的badness值(也是一种判断进程回收等级的数值),badness值大的会被收掉。

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

推荐阅读更多精彩内容