Android 中app内存回收优化(一):R版本

版本基于:Android R

0. 前言

Android Q 中新增了framework 端app 内存回收优化方案。当app 的 oom adj 发生特定变化时,framework 端会对应用的内存进行处理。随着版本的演变,这部分优化工作也一直在完善,笔者将针对Android RAndroid S 对该部分的优化流程分别进行详细地剖析。

本文针对 Android R。

**注意:**本文中提到的 "压缩" 这个词,其实指的是内存回收优化,因为只有到确切的逻辑的时候才明确到底是匿名页回收还是文件页回收,而在此之前我们暂定为 compact 处理。

1. CachedAppOptimizer 类

App 端的内存压缩管理是在 CachedAppOptimizer 类中完成。

我们在之前的博文**《oom_adj 更新原理(1)》** 、**《oom_adj 更新原理(2)》**中得知 AMS 通过 OomAdjuster 类来管理 oom_adj 的更新、计算、应用。在 OomAdjuster 中也实例了一个 CachedAppOptimizer 的对象:

java 复制代码
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
            ServiceThread adjusterThread) {
        mService = service;
        ...
        mCachedAppOptimizer = new CachedAppOptimizer(mService);
        ...
    }

参数为 AMS 对象。

下面来看下 CachedAppOptimizer 构造

java 复制代码
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public CachedAppOptimizer(ActivityManagerService am) {
        this(am, null, new DefaultProcessDependencies());
    }

    @VisibleForTesting
    CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
            ProcessDependencies processDependencies) {
        mAm = am;
        mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
            Process.THREAD_GROUP_SYSTEM, true);
        mProcStateThrottle = new HashSet<>();
        mProcessDependencies = processDependencies;
        mTestCallback = callback;
    }

构造中创建了一个 ServiceThread,名称为 CachedAppOptimizerThread,优先级为THREAD_GROUP_SYSTEM

mProcessDependencies 是 DefaultProcessDependencies类型的对象,用以最后的压缩处理。

2. init()

init() 函数是在 SystemServer.startOtherServices() 的时候调用 AMS 中installSystemProviders() 函数触发,在 installSystemProviders() 会调用 mOomAdjuster.initSettings():

java 复制代码
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    void initSettings() {
        mCachedAppOptimizer.init();
        ...
    }

下面来看下 init() 函数:

java 复制代码
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public void init() {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
        synchronized (mPhenotypeFlagLock) {
            updateUseCompaction();
            updateCompactionActions();
            updateCompactionThrottles();
            updateCompactStatsdSampleRate();
            updateFreezerStatsdSampleRate();
            updateFullRssThrottle();
            updateFullDeltaRssThrottle();
            updateProcStateThrottle();
            updateUseFreezer();
        }
    }

函数最开始设定一个 DeviceConfig 的属性变量的监听 listener,当DeviceConfig 中 namespace 为 activity_manager 的属性发生变化时,会回调 mOnFlagsChangedListener() 函数。

接着是一堆 update函数,用以初始化一些 成员变量,下面挑几个剖析下。

2.1 updateUseCompaction()

java 复制代码
    private void updateUseCompaction() {
        mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);

        if (mUseCompaction && mCompactionHandler == null) {
            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mCompactionHandler = new MemCompactionHandler();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        }
    }

首先获取下属性 use_compaction,默认值使用 DEFAULT_USE_COMPACTION (false):

java 复制代码
    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;

如果 user_compation 使能,接着就会创建 mCompactionHandler 用以异步消息处理,并且启动在构造函数中创建的ServiceThread。

来看下 MemCompactionHandler 类:

java 复制代码
    private final class MemCompactionHandler extends Handler {
        private MemCompactionHandler() {
            super(mCachedAppOptimizerThread.getLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case COMPACT_PROCESS_MSG: {
                    ...
                    break;
                }
                case COMPACT_SYSTEM_MSG: {
                    ...
                    break;
                }
            }
        }

Looper 是用的就是 ServiceThread 的Looper,主要用以处理压缩进程内存或system 进程内存。

2.2 updateCompactionActions()

java 复制代码
    private void updateCompactionActions() {
        int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);

        int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);

        mCompactActionSome = compactActionIntToString(compactAction1);
        mCompactActionFull = compactActionIntToString(compactAction2);
    }

主要是确认 some、full 压缩时执行的 action,默认情况下:

  • some 采用 DEFAULT_COMPACT_ACTION_1(file);
  • full 采用 DEFAULT_COMPACT_ACTION_2(all);
java 复制代码
    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;

我们后面在压缩的选择上依赖这两个变量,最终的压缩策略为:

  • pendingAction 为 SOME,使用 mCompactActionSome 的方式压缩;
  • pendingAction 为 FULL / PERSISTENT **/**BFGS,使用 mCompactActionFull 的方式压缩;
  • system 为 all 的方式压缩;

详细逻辑看第 4 节。

2.3 updateCompactionThrottles()

java 复制代码
    private void updateCompactionThrottles() {
        boolean useThrottleDefaults = false;
        String throttleSomeSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_1);
        String throttleSomeFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_2);
        String throttleFullSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_3);
        String throttleFullFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_4);
        String throttleBFGSFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_5);
        String throttlePersistentFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_6);

        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
                || TextUtils.isEmpty(throttleFullSomeFlag)
                || TextUtils.isEmpty(throttleFullFullFlag)
                || TextUtils.isEmpty(throttleBFGSFlag)
                || TextUtils.isEmpty(throttlePersistentFlag)) {
            // Set defaults for all if any are not set.
            useThrottleDefaults = true;
        } else {
            try {
                mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
                mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
                mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
                mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
            } catch (NumberFormatException e) {
                useThrottleDefaults = true;
            }
        }

        if (useThrottleDefaults) {
            mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
            mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
            mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
            mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
        }
    }

代码比较简单,获取 DeviceConfig 属性 KEY_COMPACT_THROTTLE_1 ~ 6

如果其中有一个属性没有设置,则全部使用默认的值:

java 复制代码
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;

用以比较上一次的压缩时间到这次压缩的时间差,为了防止连续的压缩请求,如果在限制的时间内连续请求压缩,系统是不允许的,代码直接return。

2.4 updateFullRssThrottle()

java 复制代码
    private void updateFullRssThrottle() {
        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);

        // Don't allow negative values. 0 means don't apply the throttle.
        if (mFullAnonRssThrottleKb < 0) {
            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
        }
    }

当是 anon 请求或者是all 请求时,需要考虑下匿名页剩余的限制,低于该限制值时,不会继续做压缩回收处理。

另外,从逻辑上也看出来,这个限制值不能为 负数,如果DeviceConfig 设置 KEY_COMPACT_FULL_RSS_THROTTLE_KB 为负数,即表示使用默认值 12M 做限制值。

2.5 updateFullDeltaRssThrottle()

java 复制代码
    private void updateFullDeltaRssThrottle() {
        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);

        if (mFullDeltaRssThrottleKb < 0) {
            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
        }
    }

同2.4,当是 anon 请求或者是all 请求时,会有另外一个限制。那就是上一次压缩之后的 RSS 与此次请求的 RSS 进行比较,如果这之间只有很小内存变动,表示内存需求不大或不紧急,则不会继续做内存压缩处理。

同样的,该值不能为 负数,如果DeviceConfig 设置 KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB 为负数,即表示使用默认值 8M做限制值。

2.6 updateProcStateThrottle()

java 复制代码
    private void updateProcStateThrottle() {
        String procStateThrottleString = DeviceConfig.getString(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
        if (!parseProcStateThrottle(procStateThrottleString)) {
            ...
            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
                ...
            }
        }
    }

    private boolean parseProcStateThrottle(String procStateThrottleString) {
        String[] procStates = TextUtils.split(procStateThrottleString, ",");
        mProcStateThrottle.clear();
        for (String procState : procStates) {
            try {
                mProcStateThrottle.add(Integer.parseInt(procState));
            } catch (NumberFormatException e) {
                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
                        + procState);
                return false;
            }
        }
        return true;
    }

进程状态限制,当进程处于 mProcStateThrottle中的状态时,不做回收处理。

mProcStateThrottle 的集合来自 DeviceConfig 中 KEY_COMPACT_PROC_STATE_THROTTLE 属性,该属性值中指定进程限制状态,可以是多个,用逗号隔开。如果该属性值解析失败,则会采用默认的状态值,默认的进程状态限制值为 PROCESS_STATE_RECEIVER

2.7 updateUseFreezer()

进程冻结状态初始化,后续将单独剖析下冻结优化。

3. 压缩优化发起者

压缩优化的触发分多种情形,在 CachedAppOptimizer 中,压缩优化发起者主要有:

  • compactAppSome()
  • compactAppFull()
  • compactAppPersistent()
  • compactAppBfgs()
  • compactAllSystem()

本节分别剖析下各个函数的使用以及触发的场景。

3.1 compactAppSome()

在 OomAdjuster.ApplyOomAdjLocked() 中触发:

java 复制代码
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed) {
        ...
 
        // 在bootup 阶段不压缩
        // useCompaction() 是app compact是否使能
        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
            // 如果跟上次的adj 不一样,有变化
            if (app.curAdj != app.setAdj) {
                // 当app从perceptible变为home/previous,执行some等级内存压缩
                // 当app变成cached,执行full等级内存压缩
                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                        (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                                app.curAdj == ProcessList.HOME_APP_ADJ)) {
                    mCachedAppOptimizer.compactAppSome(app);
                } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
                                || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
                        && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
                        && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
                    mCachedAppOptimizer.compactAppFull(app);
                }
            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE  //非唤醒状态
                    && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
                    && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
                //处于非唤醒状态,且上一次重要性高于前台进程adj,
                //  且上一次压缩的时间已经尝过10min或者上次没压缩都返回true,
                //  进行persistent 级别压缩
                mCachedAppOptimizer.compactAppPersistent(app);
            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                    && app.getCurProcState()
                        == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                    && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
                // 非唤醒状态,且当前进程状态为绑定一个前台service进程
                //   且上一次压缩的时间已经超过10min或上一次没压缩都返回true
                mCachedAppOptimizer.compactAppBfgs(app);
            }
        }

        ...
 
        return success;
    }

当 app compaction 功能使能,且系统完成boot 的情况下:

  • 当oom adj 发生变化:
    • 当上一次的adj (setAdj) <= PERCEPTIBLE_APP_ADJ,变成 PREVIOUS_APP_ADJHOME_APP_ADJ, 使用 ***compactAppSome()***压缩优化;
    • 当上一次的adj (setAdj) 非 cached adj,变成 cached adj,使用 compactAppFull() 压缩优化;
  • 当处于非唤醒状态,且当上一次的adj (setAdj) 重要性高于前台 adj (FOREGROUND_APP_ADJ ),且距上一次压缩间隔超过10min 或从没有压缩过,使用 compactAppPersistent() 压缩优化;
  • 当处于非唤醒状态,且应用进程状态为 PROCESS_STATE_BOUND_FOREGROUND_SERVICE ,且距上一次压缩间隔超过 10min 或从没有压缩过,使用 compactAppBfgs() 压缩优化;

下面来看下 compactAppSome() 做了些什么:

java 复制代码
    void compactAppSome(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_SOME;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
            }
        }
    }
  • 指定进程请求的压缩action 为 COMPACT_PROCESS_SOME
  • 如果进程没有处于压缩状态,则准备进入:
    • 指定 mPendingCompact 为true,标记进入压缩状态;
    • 将进程加入 mPendingCompactionProcesses 中,后续消息接受到之后从这里取需要压缩的进程;
    • 发送消息 COMPACT_PROCESS_MSG 给 mCompactionHandler 所在线程,参数为 app.setAdj 和 app.setProcState;

3.2 compactAppFull()

3.1 节,也是在 OomAdjuster.ApplyOomAdjLocked() 中触发。

java 复制代码
    void compactAppFull(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_FULL;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
            }
        }
    }

代码与compactAppSome() 差不多,这里将 compact action 换成了 COMPACT_PROCESS_FULL

3.3 compactAppPersistent()

3.1 节,也是在 OomAdjuster.ApplyOomAdjLocked() 中触发。

java 复制代码
    void compactAppPersistent(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
            }
        }
    }

代码与compactAppSome() 差不多,这里将 compact action 换成了 COMPACT_PROCESS_PERSISTENT

3.4 compactAppBfgs()

3.1 节,也是在 OomAdjuster.ApplyOomAdjLocked() 中触发。

java 复制代码
    void compactAppBfgs(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_BFGS;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
            }
        }
    }

代码与compactAppSome() 差不多,这里将 compact action 换成了 COMPACT_PROCESS_BFGS

3.5 compactAllSystem()

该函数的触发与上面 4 个不同,触发分两部分场景:

  • AMS 中调用 finishBooting() 时,即系统启动完成后对系统所有进程进行全面压缩;
  • MountServiceIdler 在每一次 startJob() 的时候调用 AMS.performIdleMaintenance(),进行系统压缩;
java 复制代码
    void compactAllSystem() {
        if (mUseCompaction) {
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                              COMPACT_SYSTEM_MSG));
        }
    }

给 mCompactionHandler 所在线程发送 COMPACT_SYSTEM_MSG消息。

4. 压缩消息处理

上一节中剖析了压缩优化的触发过程,最终都是通过给 mCompactionHandler 发送消息完成压缩优化的指令,本节来看下mCompactionHandler对不同消息的处理流程。

java 复制代码
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case COMPACT_PROCESS_MSG: {
                    // 记录此次压缩处理的起始时间
                    long start = SystemClock.uptimeMillis();
                    ProcessRecord proc;
                    int pid;
                    String action;
                    final String name;
                    int pendingAction, lastCompactAction;
                    long lastCompactTime;
                    LastCompactionStats lastCompactionStats;
                    // 获取消息带过来的参数
                    int lastOomAdj = msg.arg1;
                    int procState = msg.arg2;
                    synchronized (CachedAppOptimizer.this) {
                        // 取出队列中的第一个进程
                        proc = mPendingCompactionProcesses.remove(0);

                        // 获取压缩 action
                        pendingAction = proc.reqCompactAction;
                        // 获取进程pid
                        pid = proc.mPidForCompact;
                        // 获取进程名
                        name = proc.processName;
                        // 进程进入压缩,该标记可以置回false,等待下一次压缩
                        proc.mPendingCompact = false;

                        // 对于 SOME 和 FULL 的action,如果进程的setAdj 小于可感知,不做处理
                        // 我们从applyOomAdjLocked() 得知,SOME 只针对HOME/PREVIOUS adj,
                        //   而FULL 只针对 cached adj 的进程
                        if ((pendingAction == COMPACT_PROCESS_SOME
                                || pendingAction == COMPACT_PROCESS_FULL)
                                && (proc.mSetAdjForCompact <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
                            return;
                        }

                        // 获取上一次压缩的信息
                        lastCompactAction = proc.lastCompactAction;
                        lastCompactTime = proc.lastCompactTime;
                        lastCompactionStats = mLastCompactionStats.get(pid);
                    }

                    // 不接受pid 为0的进程
                    if (pid == 0) {
                        // not a real process, either one being launched or one being killed
                        return;
                    }

                    // basic throttling
                    // use the Phenotype flag knobs to determine whether current/prevous
                    // compaction combo should be throtted or not

                    // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
                    // should very seldom change, and taking the risk of using the wrong action is
                    // preferable to taking the lock for every single compaction action.

                    // 开始一些限制条件的过滤
                    // 限制条件1,应用已经执行过 compaction,确认与上一次压缩的时间间隔是否在限制内
                    if (lastCompactTime != 0) {
                        if (pendingAction == COMPACT_PROCESS_SOME) {
                            if ((lastCompactAction == COMPACT_PROCESS_SOME
                                    && (start - lastCompactTime < mCompactThrottleSomeSome))
                                    || (lastCompactAction == COMPACT_PROCESS_FULL
                                        && (start - lastCompactTime
                                                < mCompactThrottleSomeFull))) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_FULL) {
                            if ((lastCompactAction == COMPACT_PROCESS_SOME
                                    && (start - lastCompactTime < mCompactThrottleFullSome))
                                    || (lastCompactAction == COMPACT_PROCESS_FULL
                                        && (start - lastCompactTime
                                                < mCompactThrottleFullFull))) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
                            if (start - lastCompactTime < mCompactThrottlePersistent) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_BFGS) {
                            if (start - lastCompactTime < mCompactThrottleBFGS) {
                                return;
                            }
                        }
                    }

                    switch (pendingAction) {
                        case COMPACT_PROCESS_SOME:
                            action = mCompactActionSome;
                            break;
                        // For the time being, treat these as equivalent.
                        case COMPACT_PROCESS_FULL:
                        case COMPACT_PROCESS_PERSISTENT:
                        case COMPACT_PROCESS_BFGS:
                            action = mCompactActionFull;
                            break;
                        default:
                            action = COMPACT_ACTION_NONE;
                            break;
                    }

                    // 限制条件2,action 不能为 NONE
                    if (COMPACT_ACTION_NONE.equals(action)) {
                        return;
                    }

                    // 限制条件3,进程的procState 不能在限制的state 集合内
                    if (mProcStateThrottle.contains(procState)) {
                        return;
                    }

                    long[] rssBefore = mProcessDependencies.getRss(pid);
                    long anonRssBefore = rssBefore[2];

                    // 限制条件4,确定RSS是否不正常
                    if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
                            && rssBefore[3] == 0) {
                        return;
                    }

                    // 限制条件5,对于FULL 压缩,或者匿名页压缩
                    // 对于RSS 使用小于 12MB 的进程,不做压缩处理;
                    // 对于上一次压缩时 RSS 与此次压缩时RSS,相差小于 8MB 的进程,不做压缩处理
                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
                        if (mFullAnonRssThrottleKb > 0L
                                && anonRssBefore < mFullAnonRssThrottleKb) {
                            return;
                        }

                        if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
                            long[] lastRss = lastCompactionStats.getRssAfterCompaction();
                            long absDelta = Math.abs(rssBefore[1] - lastRss[1])
                                    + Math.abs(rssBefore[2] - lastRss[2])
                                    + Math.abs(rssBefore[3] - lastRss[3]);
                            if (absDelta <= mFullDeltaRssThrottleKb) {
                                return;
                            }
                        }
                    }

                    /*************限制条件全部通过,进入compact 流程**********************/

                    // 更新压缩计数
                    switch (pendingAction) {
                        case COMPACT_PROCESS_SOME:
                            mSomeCompactionCount++;
                            break;
                        case COMPACT_PROCESS_FULL:
                            mFullCompactionCount++;
                            break;
                        case COMPACT_PROCESS_PERSISTENT:
                            mPersistentCompactionCount++;
                            break;
                        case COMPACT_PROCESS_BFGS:
                            mBfgsCompactionCount++;
                            break;
                        default:
                            break;
                    }
                    try {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
                                + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
                                + ": " + name);
                        long zramFreeKbBefore = Debug.getZramFreeKb();

                        // 压缩处理的核心函数 performCompaction()
                        mProcessDependencies.performCompaction(action, pid);

                        // 压缩完成之后,进行一些数据的记录
                        long[] rssAfter = mProcessDependencies.getRss(pid);
                        long end = SystemClock.uptimeMillis();
                        long time = end - start;
                        long zramFreeKbAfter = Debug.getZramFreeKb();
                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
                                rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
                                rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
                                lastCompactAction, lastCompactTime, lastOomAdj, procState,
                                zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
                        // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
                        // on every single compaction for a flag that will seldom change and the
                        // impact of reading the wrong value here is low.
                        if (mRandom.nextFloat() < mCompactStatsdSampleRate) {
                            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPACTED, pid, name,
                                    pendingAction, rssBefore[0], rssBefore[1], rssBefore[2],
                                    rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2],
                                    rssAfter[3], time, lastCompactAction, lastCompactTime,
                                    lastOomAdj, ActivityManager.processStateAmToProto(procState),
                                    zramFreeKbBefore, zramFreeKbAfter);
                        }
                        synchronized (CachedAppOptimizer.this) {
                            proc.lastCompactTime = end;
                            proc.lastCompactAction = pendingAction;
                        }
                        if (action.equals(COMPACT_ACTION_FULL)
                                || action.equals(COMPACT_ACTION_ANON)) {
                            // Remove entry and insert again to update insertion order.
                            mLastCompactionStats.remove(pid);
                            mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
                        }
                    } catch (Exception e) {
                        // nothing to do, presumably the process died
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                }
                case COMPACT_SYSTEM_MSG: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
                    compactSystem();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                }
            }
        }
    }

代码虽然多,但逻辑还是比较清晰的,详细流程见代码中注释,这里主要注意两点。

第一点,最终的压缩策略选择为:

  • pendingAction 为 SOME,使用 mCompactActionSome 的方式压缩;
  • pendingAction 为 FULL / PERSISTENT **/**BFGS,使用 mCompactActionFull 的方式压缩;
  • system 为 all 的方式压缩;

第二点,最终压缩的处理主要两个方式:

  • mProcessDependencies.performCompaction();
  • compactSystem();

4.1 performCompaction()

java 复制代码
        public void performCompaction(String action, int pid) throws IOException {
            try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
                fos.write(action.getBytes());
            }
        }

从这里看到,压缩的核心就 process_reclaim 驱动,向 /proc/PID/reclaim节点中写入对应的action,由驱动来进行最重要的压缩处理。

4.2 compactSystem()

这是一个 native 的函数:

cpp 复制代码
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp

static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
    struct dirent* current;
    while ((current = readdir(proc.get()))) {
        if (current->d_type != DT_DIR) {
            continue;
        }

        // don't compact system_server, rely on persistent compaction during screen off
        // in order to avoid mmap_sem-related stalls
        if (atoi(current->d_name) == getpid()) {
            continue;
        }

        std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
        struct stat status_info;

        if (stat(status_name.c_str(), &status_info) != 0) {
            // must be some other directory that isn't a pid
            continue;
        }

        // android.os.Process.FIRST_APPLICATION_UID
        if (status_info.st_uid >= 10000) {
            continue;
        }

        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
        WriteStringToFile(std::string("all"), reclaim_path);
    }
}

逻辑一堆,最终就是给所有的节点 /proc/PID/reclaim 写 all,按照 all 的方式压缩处理;

5. process_reclaim 驱动

cpp 复制代码
fs/proc/internal.h

extern const struct file_operations proc_reclaim_operations;
cpp 复制代码
fs/proc/base.c

#ifdef CONFIG_PROCESS_RECLAIM
	REG("reclaim", 0222, proc_reclaim_operations),
#endif

proc 文件系统多加一个节点,对应的 operation 为proc_reclaim_operations

cpp 复制代码
fs/proc/task_mmu.c

const struct file_operations proc_reclaim_operations = {
	.write		= reclaim_write,
	.llseek		= noop_llseek,
};

write 的处理函数 reclaim_write():

cpp 复制代码
fs/proc/task_mmu.c

num reclaim_type {
	RECLAIM_FILE,
	RECLAIM_ANON,
	RECLAIM_ALL,
	RECLAIM_RANGE,
};

static ssize_t reclaim_write(struct file *file, const char __user *buf,
				size_t count, loff_t *ppos)
{
	struct task_struct *task;
	char buffer[200];
	struct mm_struct *mm;
	struct vm_area_struct *vma;
	enum reclaim_type type;
	char *type_buf;
	unsigned long start = 0;
	unsigned long end = 0;
	const struct mm_walk_ops reclaim_walk_ops = {
		.pmd_entry = reclaim_pte_range,
	};

	memset(buffer, 0, sizeof(buffer));
	if (count > sizeof(buffer) - 1)
		count = sizeof(buffer) - 1;

	if (copy_from_user(buffer, buf, count))
		return -EFAULT;

	type_buf = strstrip(buffer);
	if (!strcmp(type_buf, "file"))
		type = RECLAIM_FILE;
	else if (!strcmp(type_buf, "anon"))
		type = RECLAIM_ANON;
	else if (!strcmp(type_buf, "all"))
		type = RECLAIM_ALL;
	else if (isdigit(*type_buf))
		type = RECLAIM_RANGE;
	else
		goto out_err;

	if (type == RECLAIM_RANGE) {
		char *token;
		unsigned long long len, len_in, tmp;

		token = strsep(&type_buf, " ");
		if (!token)
			goto out_err;
		tmp = memparse(token, &token);
		if (tmp & ~PAGE_MASK || tmp > ULONG_MAX)
			goto out_err;
		start = tmp;

		token = strsep(&type_buf, " ");
		if (!token)
			goto out_err;
		len_in = memparse(token, &token);
		len = (len_in + ~PAGE_MASK) & PAGE_MASK;
		if (len > ULONG_MAX)
			goto out_err;
		/*
		 * Check to see whether len was rounded up from small -ve
		 * to zero.
		 */
		if (len_in && !len)
			goto out_err;

		end = start + len;
		if (end < start)
			goto out_err;
	}

	task = get_proc_task(file->f_path.dentry->d_inode);
	if (!task)
		return -ESRCH;

	mm = get_task_mm(task);
	if (!mm)
		goto out;

	down_read(&mm->mmap_sem);
	if (type == RECLAIM_RANGE) {
		vma = find_vma(mm, start);
		while (vma) {
			if (vma->vm_start > end)
				break;
			if (is_vm_hugetlb_page(vma))
				continue;

			walk_page_range(mm, max(vma->vm_start, start),
					min(vma->vm_end, end),
					&reclaim_walk_ops, vma);
			vma = vma->vm_next;
		}
	} else {
        //遍历当前进程所占用的虚拟地址
		for (vma = mm->mmap; vma; vma = vma->vm_next) {
			if (is_vm_hugetlb_page(vma))
				continue;

            // anon 压缩的,只关心匿名页
			if (type == RECLAIM_ANON && vma->vm_file)
				continue;

            //file 压缩的,只关心文件页
			if (type == RECLAIM_FILE && !vma->vm_file)
				continue;

            // 遍历页表,并调用回调函数 reclaim_walk_ops() 处理
			walk_page_range(mm, vma->vm_start, vma->vm_end,
					&reclaim_walk_ops, vma);
		}
	}

	flush_tlb_mm(mm);
	up_read(&mm->mmap_sem);
	mmput(mm);
out:
	put_task_struct(task);
	return count;

out_err:
	return -EINVAL;
}

逻辑比较简单,从上层传入到节点的值 可能是:

  • file
  • anon
  • all
  • 其他

驱动中对应的 reclaim_type 分别是:

  • RECLAIM_FILE
  • RECLAIM_ANON
  • RECLAIM_ALL
  • RECLAIM_RANGE

通过 get_proc_task() 获取进程的 task_struct,进而获取到进程的 mm_struct,进而可以获取进程的 vma,通过遍历vma,对进程的file、anon 进行对应的处理,最终调用 walk_page_range(),该函数参数:

  • 指定了进程mm_struct;
  • vma 的 start 和 end;
  • mm_walk_ops 为 reclaim_walk_ops;

walk_page_range() 最终会回调到 reclaim_walk_ops.pmd_entry,即reclaim_pte_range()

cpp 复制代码
fs/proc/task_mmu.c

#ifdef CONFIG_PROCESS_RECLAIM
static int reclaim_pte_range(pmd_t *pmd, unsigned long addr,
				unsigned long end, struct mm_walk *walk)
{
	struct vm_area_struct *vma = walk->private;
	pte_t *pte, ptent;
	spinlock_t *ptl;
	struct page *page;
	LIST_HEAD(page_list);
	int isolated;

	split_huge_pmd(vma, addr, pmd);
	if (pmd_trans_unstable(pmd))
		return 0;
cont:
	isolated = 0;
	pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
	for (; addr != end; pte++, addr += PAGE_SIZE) {
		ptent = *pte;
		if (!pte_present(ptent))
			continue;

		page = vm_normal_page(vma, addr, ptent);
		if (!page)
			continue;

		if (isolate_lru_page(compound_head(page)))
			continue;

		/* MADV_FREE clears pte dirty bit and then marks the page
		 * lazyfree (clear SwapBacked). Inbetween if this lazyfreed page
		 * is touched by user then it becomes dirty.  PPR in
		 * shrink_page_list in try_to_unmap finds the page dirty, marks
		 * it back as PageSwapBacked and skips reclaim. This can cause
		 * isolated count mismatch.
		 */
		if (PageAnon(page) && !PageSwapBacked(page)) {
			putback_lru_page(page);
			continue;
		}

		list_add(&page->lru, &page_list);
		inc_node_page_state(page, NR_ISOLATED_ANON +
				page_is_file_lru(page));
		isolated++;
		if (isolated >= SWAP_CLUSTER_MAX)
			break;
	}
	pte_unmap_unlock(pte - 1, ptl);
	reclaim_pages_from_list(&page_list, vma);
	if (addr != end)
		goto cont;

	cond_resched();
	return 0;
}

最终调用 reclaim_pages_from_list() 函数:

cpp 复制代码
mm/vmscan.c

#ifdef CONFIG_PROCESS_RECLAIM
unsigned long reclaim_pages_from_list(struct list_head *page_list,
			struct vm_area_struct *vma)
{
	struct scan_control sc = {
		.gfp_mask = GFP_KERNEL,
		.priority = DEF_PRIORITY,
		.may_writepage = 1,
		.may_unmap = 1,
		.may_swap = 1,
		.target_vma = vma,
	};

	unsigned long nr_reclaimed;
	struct reclaim_stat stat;
	struct page *page;

	list_for_each_entry(page, page_list, lru)
		ClearPageActive(page);

	nr_reclaimed = shrink_page_list(page_list, NULL, &sc,
			TTU_IGNORE_ACCESS, &stat, true);

	while (!list_empty(page_list)) {
		page = lru_to_page(page_list);
		list_del(&page->lru);
		dec_node_page_state(page, NR_ISOLATED_ANON +
				page_is_file_lru(page));
		putback_lru_page(page);
	}

	return nr_reclaimed;
}
#endif

详细的 shrink_page_list() 可以查看**《shrink_list 详解》** 一文第4节。

其他的process reclaim patch这里就不一一列举,详细可以点击:这里,进行下载。

至此,Android R 版本中关于 app 回收优化就全部剖析完成,下面做个总结:

  • 使用CachedAppOptimizer 对app compaction 进行管理,触发compact 接口主要分:
    • 当adj 发生变化时,如果上一次adj <= PERCEPTIBLE_APP_ADJ,变成 PREVIOUS 或 HOME,使用 SOME 压缩优化;
    • 当adj 发生变化时,如果上一次非 CACHED,变成 CACHED,使用 FULL 压缩优化;
    • 当处于非唤醒状态,且当上一次的adj (setAdj) 重要性高于前台 adj (FOREGROUND_APP_ADJ ),且距上一次压缩间隔超过10min 或从没有压缩过,使用 compactAppPersistent() 压缩优化;
    • 当处于非唤醒状态,且应用进程状态为 PROCESS_STATE_BOUND_FOREGROUND_SERVICE ,且距上一次压缩间隔超过 10min 或从没有压缩过,使用 compactAppBfgs() 压缩优化;
    • AMS 中调用 finishBooting() 时,即系统启动完成后 对系统所有进程进行全面压缩,调用 compactAllSystem()
    • MountServiceIdler 在每一次 startJob() 的时候调用 AMS**.** performIdleMaintenance(),***compactAllSystem()***进行系统压缩;
  • CachedAppOptimizer 中维护一个 ServiceThread,几种处理compact 调用后发出的消息;
  • 非 system 压缩,都需要经过很多限制条件之后,才能正式进入压缩处理函数;
  • 在压缩前会对 compact action 进行合并,SOME 使用的的是 mCompactActionSome 压缩方式,FULL / PERSISTENT / BFGS 使用的都是 mCompactActionFull 压缩方式;
  • 压缩处理函数分:
    • performCompaction(),写 /proc/PID/reclaim 节点,需要驱动支持;
    • compactSystem(),这个是native 的调用,最终也是写 /proc/PID/reclaim 节点;
    • **Android S**开始将压缩处理函数,都放native 中实现;

关于Andoid S 版本的压缩优化,有很多变动,可以查看**下一篇博文**。

相关推荐
百锦再26 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗38 分钟前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO44 分钟前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu4 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci