Android 中app内存压缩优化(一):R版本

这篇具有很好参考价值的文章主要介绍了Android 中app内存压缩优化(一):R版本。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

版本基于:Android R

0. 前言

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

本文针对 Android R。

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

1. CachedAppOptimizer 类

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

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

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 构造

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():

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

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

下面来看下 init() 函数:

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()

    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):

    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;

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

来看下 MemCompactionHandler 类:

    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()

    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);
    @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()

    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

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

    @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()

    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()

    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()

    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() 中触发:

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_ADJ 或 HOME_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() 做了些什么:

    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() 中触发。

    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() 中触发。

    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() 中触发。

    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(),进行系统压缩;
    void compactAllSystem() {
        if (mUseCompaction) {
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                              COMPACT_SYSTEM_MSG));
        }
    }

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

4. 压缩消息处理

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

        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()

        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 的函数:

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 驱动

fs/proc/internal.h

extern const struct file_operations proc_reclaim_operations;
fs/proc/base.c

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

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

fs/proc/task_mmu.c

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

write 的处理函数 reclaim_write():

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()

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() 函数:

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 版本的压缩优化,有很多变动,可以查看下一篇博文文章来源地址https://www.toymoban.com/news/detail-560738.html

到了这里,关于Android 中app内存压缩优化(一):R版本的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 可测含多进程的app-- python调用adb命令获取Android App应用的性能数据:CPU、GPU、内存、电池、耗电量(含python源码)

    python脚本通过os.popen()方法运行adb命令,获取性能数据,将性能数据保存在csv文件并算出均值、最大值、最小值。 本脚本可测试一个app包含多个进程的场景,可以获取 每个进程的性能数据 。 2.1 软件环境 具备python环境,Android环境 需要python库:os, csv, time, datetime, sys,time,panda

    2024年02月13日
    浏览(44)
  • Android安卓实战项目(12)—关于身体分析,BMI计算,喝水提醒,食物卡路里计算APP【支持中英文切换】生活助手类APP(源码在文末)

    B站演示 【Android安卓实战项目(12)—生活助手类APP—关于身体分析,BMI计算,喝水提醒,食物卡路里计算APP【支持中英文切换】】 https://www.bilibili.com/video/BV1Wu4y1C76j/?share_source=copy_webvd_source=b2e9b9ed746acda34f499009647748ed 这段代码是一个Android应用程序的主要活动(Activity),它是一

    2024年02月10日
    浏览(46)
  • Android图片压缩原理分析(三)—— 哈夫曼压缩讲解

      前面几篇文章,我们了解了一些关于图片压缩的基础知识以及Android的Bitmap相关的知识,然后也提到的 Skia 是 Android 的重要组成部分。在鲁班压缩算法解析中初次提到了哈夫曼压缩,那么他们之间到底是存在什么关系呢?今天我们就来探究探究。 什么是skia图像引擎了,详细

    2024年02月11日
    浏览(37)
  • 【Android 性能优化:内存篇】——WebView 内存泄露治理

    背景:笔者在公司项目中优化内存泄露时发现WebView 相关的内存泄露问题非常经典,一个 Fragment 页面使用的 WebView 有多条泄露路径,故记录下。 项目中一个Fragment 使用 Webview,在 Fragment onDestroyView 时候却没有释放,释放 WebView 还不简单嘛,于是笔者在 Fragment 的 onDestroyView 补充

    2024年02月04日
    浏览(45)
  • Android 性能优化——APP启动优化

            首先在《Android系统和APP启动流程》中我们介绍了 APP 的启动流程,但都是 FW 层的流程,这里我们主要分析一下在 APP 中的启动流程。要了解 APP 层的启动流程,首先要了解 APP 启动的分类。 冷启动         应用从头开始启动,即应用的首次启动。需要做大量的工

    2024年04月12日
    浏览(47)
  • 【Android内存优化】内存泄露优化之强引用变弱引用完全详解

    内存空间使用完毕后无法被释放的现象,对于还保持着引用, 该内存不能再被分配使用,逻辑上却已经不会再用到的对象,垃圾回收器不会回收它们。 所以逻辑不再使用的对象,需要释放强引用,以便GC进行回收。 JVM 垃圾回收原理,点击进入 常见Handler 写法 This Handler clas

    2024年02月08日
    浏览(47)
  • android源码学习- APP启动流程(android12源码)

    百度一搜能找到很多讲APP启动流程的,但是往往要么就是太老旧(还是基于android6去分析的),要么就是不全(往往只讲了整个流程的一小部分)。所以我结合网上现有的文章,以及源码的阅读和调试,耗费了3整天的时间,力求写出一篇最完整,最详细,最通俗易懂的文章,

    2024年02月11日
    浏览(46)
  • Android 开发的五大开源网站,安卓内存优化面试

    (4) 多快捷键支持 ① 左右翻页 在项目(搜索)列表及详情页左手党可以通过 awsd,右手党可通过上下左右键或者 nl 键翻页,浏览项目从未有过的流畅体验。 ② 快速打标签 项目详情页可通过 t 快速进入新增标签输入框,回车确定标签,Esc 退出编辑。 ③ 快速搜索 项目列表页可通

    2024年04月09日
    浏览(59)
  • 【Android】APP启动优化学习笔记

    用户体验: 应用的启动速度直接影响用户体验。用户希望应用能够快速启动并迅速响应他们的操作。如果应用启动较慢,用户可能会感到不满,并且有可能选择卸载或切换到竞争对手的应用。通过启动优化,可以提高应用的启动速度,让用户获得更好的使用体验。 竞争优势

    2024年02月14日
    浏览(41)
  • 【Android】app应用内版本更新升级(DownloadManager下载,适配Android6.0以上所有版本)

    版本的升级和更新是一个线上App所必备的功能,App的升级安装包主要通过 应用商店 或者 应用内下载 两种方式获得,大部分app这两种方式都会具备,应用商店只需要上传对应平台审核通过即可,而应用内更新一般是通过以下几种方式: 1.集成第三方库如 appupdateX、bugly 的更新

    2024年02月11日
    浏览(114)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包