Android OOM Adjustments

之前有写过文章分析LMK,那篇主要是分析LMK实现原理,并没有仔细分析AMS中OOM Adj的调整。这次参考Android 9.0的代码来分析一下,主要是分析代码实现。首先看一下OOM Adj的定义都有哪些。

Adj Value Comments
UNKNOWN_ADJ 1001 无法确定的Adj,通常是将要缓存的进程
CACHED_APP_MAX_ADJ 906 不可见进程的Adj最大值
CACHED_APP_MIN_ADJ 900 不可见进程的Adj最小值
SERVICE_B_ADJ 800 B List中的Service,和A list相比,他们对用户的黏合度要小些
PREVIOUS_APP_ADJ 700 用户前一次交互的进程
HOME_APP_ADJ 600 Launcher进程
SERVICE_ADJ 500 应用服务进程
HEAVY_WEIGHT_APP_ADJ 400 后台的重量级进程
BACKUP_APP_ADJ 300 承载backup相关操作的进程
PERCEPTIBLE_APP_ADJ 200 可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ 100 前台可见的Activity进程
FOREGROUND_APP_ADJ 0 当前正在前台运行的进程,也就是用户正在交互的那个程序
PERSISTENT_SERVICE_ADJ -700 与系统进程或Persistent进程绑定的进程
PERSISTENT_PROC_ADJ -800 Persistent属性的进程,如telephony
SYSTEM_ADJ -900 系统进程
NATIVE_ADJ -1000 Native进程,不被系统管理

updateOomAdj

OOM Adj的更新是通过AMS中updateOomAdjLocked() 函数完成的。这个函数中不仅仅更新了OOM Adj,同时还进行了内存调整。我们先看一下OOM Adj的调整实现。

    final void updateOomAdjLocked() {
        ......
        boolean retryCycles = false;

        // 因为service连接,需要重置进程的cycle状态,
        for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            app.containsCycle = false;
        }
        for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                app.procStateChanged = false;
                // 计算app的OOM adj
                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
                
                // 如果任意一个进程处于cycle中,需要增加一次循环
                retryCycles |= app.containsCycle;
                
                // 对于没有分配Adj的后台缓存进程,在这里进行分配。
                if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
                    ......
                }
            }
        }
        
        // 存在处于cycle中的进程时,重新计数OOM Adj,直到没有进程提高优先级
        int cycleCount = 0;
        while (retryCycles) {
            cycleCount++;
            retryCycles = false;

            for (int i=0; i=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                // 设置进程的OOM Adj
                applyOomAdjLocked(app, true, now, nowElapsed);
                
                // 统计各种进程类型的数量,并杀掉超过限制的后台缓存进程和empty进程
                switch (app.curProcState) {
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                           ......
                        break;
                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                        ......
                        break;
                    default:
                        mNumNonCachedProcs++;
                        break;
                }

                if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
                    // 杀掉孤立进程
                    app.kill("isolated not needed", true);
                } else {
                    // 保留的进程,更新uid
                    final UidRecord uidRec = app.uidRecord;
                    ......
                }

                // 统计进程状态大于HOME的数量,也就是不太重要的进程
                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                        && !app.killedByAm) {
                    numTrimming++;
                }
            }
        }

        // 检查是否有uid从后台切换到前台或从前台切换到后台,并通知需要去阻止的应用
        incrementProcStateSeqAndNotifyAppsLocked();

        mNumServiceProcs = mNewNumServiceProcs;
        ......
        // 进行内存调整和回收
        ......
        // 如果设置总是销毁后台Activity
        if (mAlwaysFinishActivities) {
            mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");
        }
               
        if (allChanged) {
            requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());
        }
        
        ArrayList becameIdle = null;

        // 更新UidRecord
        if (mLocalPowerManager != null) {
            mLocalPowerManager.startUidChanges();
        }
        for (int i=mActiveUids.size()-1; i>=0; i--) {
            ......
        }
        if (mLocalPowerManager != null) {
            mLocalPowerManager.finishUidChanges();
        }
        ......
    }

OOM Adj的更新过程主要完成以下工作,

  • 重新计算进程的OOM Adj值,并进行更新。
  • 未分配Adj值的进程根据进程状态分为后台缓存进程和empty进程,在CACHED_APP_MIN_ADJ到CACHED_APP_MAX_ADJ分配Adj值。后台缓存进程和empty进程的Adj值交叉递增,每一个级别上的进程个数都不超过预先计算的最大值。
  • 逆序处理LRU中的进程,回收超过限制的后台缓存进程和empty进程。默认的限制时后台缓存进程和empty进程各16个。
  • isolated进程果已经不包含服务,直接回收。
  • 更新进程的UidRecord。

接下来分析一下updateOomAdjLocked() 中关于内存调整的部分。Android将系统内存的状态分为了4个等级,定义如下。

Adj Value Comments
ADJ_MEM_FACTOR_NORMAL 0 系统内存正常,不需要调整
ADJ_MEM_FACTOR_MODERATE 1 系统内存中等,低于正常状态
ADJ_MEM_FACTOR_LOW 2 系统内存低,需要回收内存
ADJ_MEM_FACTOR_CRITICAL 3 系统内存紧张,必须回收些内存

在ComponentCallbacks2中还定义了内存回收的级别,其中前三个是后台缓存的回收级别,后三个是进程运行时的回收级别。

Adj Value Comments
TRIM_MEMORY_COMPLETE 80 处于后台LRU列表尾部的进程,如果找不到更多内存,很快将被杀死。
TRIM_MEMORY_MODERATE 60 处于后台LRU列表中部的进程,清理内存可以让后续运行的进程获得更好的性能。
TRIM_MEMORY_BACKGROUND 40 后台进程,处于LRU列表的头部,这时清理内存可以让进程更高效的返回前台。
TRIM_MEMORY_UI_HIDDEN 20 进程UI已经不可见,可以释放UI资源。
TRIM_MEMORY_RUNNING_CRITICAL 15 设备正运行在低内存上,无法保证后台进程存活。应该尽可能的释放非关键资源。接下来要调用onLowMemory()报告系统内存低,已经显著影响用户
TRIM_MEMORY_RUNNING_LOW 10 设备正运行在低内存上,应释放不必要的资源。
TRIM_MEMORY_RUNNING_MODERATE 5 设备的运行内存偏低,可能需要释放不必要的资源

Android系统是根据后台缓存进程和empty进程的数量来区分内存等级的。因为系统总是尽可能多的保留后台进程,以便于进程再次启动时可以减少启动时间,用户体验更好。但当系统内存不足时,lowmemeorykiller机制会优先杀死不重要的后台进程,所以可以认为后台进程的数量是与lowmemrorykiller的触发挂钩的。剩余的后台进程越少,表明通过Lowmemroykiller需要回收的内存越多,整个系统的内存就越紧张。

    final void updateOomAdjLocked() {
        ......
        // 后台缓存进程与empty进程的总和
        final int numCachedAndEmpty = numCached + numEmpty;
        int memFactor;
        // 只有cache进程和empty进程同时小于各自的TRIM值时,才认为存在内存不足的情况
        // 默认情况下CUR_TRIM_EMPTY_PROCESSES=8,CUR_TRIM_CACHED_PROCESSES=5
        if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
                && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
            if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
                // 当cache+empty进程数小于3时,表明系统内存紧张
                memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
            } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
                // 当cache+empty进程数小于5时,表明系统内存低
                memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
            } else {
                // 其他情况表明内存中等
                memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
            }
        } else {
            memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
        }
        ......
        mLastMemoryLevel = memFactor;
        mLastNumProcesses = mLruProcesses.size();
        // 设置内存调整等级,如果成功返回true
        boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleepingLocked(), now);
        final int trackerMemFactor = mProcessStats.getMemFactorLocked();
        // 内存不处于正常级别时,需要回收内存
        if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
            if (mLowRamStartTime == 0) {
                mLowRamStartTime = now;
            }
            int step = 0;
            int fgTrimLevel;
            // 根据内存等级获取fgTrimLevel,在ComponentCallbacks2定义
            switch (memFactor) {
                ......
            }
            // 计算factory,用于每个trimLevel上的进程数
            int factor = numTrimming/3;
            int minFactor = 2;
            if (mHomeProcess != null) minFactor++;
            if (mPreviousProcess != null) minFactor++;
            if (factor < minFactor) factor = minFactor;
            // 默认的trimLevel,为最高
            int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
            // 逆序处理LRU中的所有进程
            for (int i=N-1; i>=0; i--) {
                ProcessRecord app = mLruProcesses.get(i);
                ......
                // 处理不太重要的而进程,进程状态大于HOME
                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                        && !app.killedByAm) {
                    // 进程的trimLevel小于当前级别,则进行回收
                    if (app.trimMemoryLevel < curLevel && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(curLevel);
                        } catch (RemoteException e) {
                        }
                        ......
                    }
                    // 更新进程的trimLevel,根据factor逐渐降低级别:COMPLETE->MODERATE->BACKGROUND
                    app.trimMemoryLevel = curLevel;
                    step++;
                    if (step >= factor) {
                        ......
                    }
                } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
                        && !app.killedByAm) {
                    // heavy weight进程以TRIM_MEMORY_BACKGROUND时进行回收
                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
                            && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(
                                    ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                        } catch (RemoteException e) {
                        }
                    }
                    app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
                } else {
                    // 进程处于后台并带有UI时,以TRIM_MEMORY_UI_HIDDEN进行回收
                    if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                            || app.systemNoUi) && app.pendingUiClean) {
                        final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
                        if (app.trimMemoryLevel < level && app.thread != null) {
                            try {
                                app.thread.scheduleTrimMemory(level);
                            } catch (RemoteException e) {
                            }
                        }
                        app.pendingUiClean = false;
                    }
                    // 当fgTrimLevel大于当前trimLevel时,以fgTrimLevel进行回收
                    if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(fgTrimLevel);
                        } catch (RemoteException e) {
                        }
                    }
                    app.trimMemoryLevel = fgTrimLevel;
                }
            }
        } else {
            // 内存正常时的处理
            if (mLowRamStartTime != 0) {
                mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
                mLowRamStartTime = 0;
            }
            for (int i=N-1; i>=0; i--) {
                ProcessRecord app = mLruProcesses.get(i);
                if (allChanged || app.procStateChanged) {
                    setProcessTrackerStateLocked(app, trackerMemFactor, now);
                    app.procStateChanged = false;
                }
                // 后台带有UI的进程以TRIM_MEMORY_UI_HIDDEN进行回收
                if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                        || app.systemNoUi) && app.pendingUiClean) {
                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                            && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(
                                    ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
                        } catch (RemoteException e) {
                        }
                    }
                    app.pendingUiClean = false;
                }
                app.trimMemoryLevel = 0;
            }
        }
        ......
    }

内存调整的主要工作是在不杀死进程的情况下,根据需要对内存进行回收。

  • 内存低时,对于进程状态大于HOME的不太重要的进程,根据LRU倒序内存回收级别逐渐降低。
  • 内存低时,对于重要的进程,越重要内存回收等级越高。
  • 内存正常时,对后台带有UI的进程进行内存回收。

computeOomAdj

上面分析updateOomAdjLocked() 的大致流程,接着分析一下其中的一个重要函数computeOomAdjLocked(),是如何计算OOM Adj的。

    private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
            ProcessRecord TOP_APP, boolean doingAll, long now) {
        // 判断Adj序列号,相等表示已经计算过或在计算中
        if (mAdjSeq == app.adjSeq) {
            ......
        }
        
        // 设置空进程的Adj
        if (app.thread == null) {
            ......
        }
        ......
        // 计算Adj最大值小于FOREGROUND进程的Adj,系统进程或Persistent进程
        if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
            // 设置Adj,SchedGroup,ProcState,UI状态等
            ......
            app.curAdj = app.maxAdj;
            app.completedAdjSeq = app.adjSeq;
            // 如果Adj小于计算前的值,则进程Adj被提升
            return app.curAdj < prevAppAdj;
        }
        ......
        // 根据进程的状态设置相应的Adj,SchedGroup,ProcState
        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
            // 前台进程
            ......
        } else if (app.runningRemoteAnimation) {
            // 正在运行远端的动画
            ......
        } else if (app.instr != null) {
            // 正在运行测试程序
            ......
        } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
            // 正在处理广播
            ......
        } else if (app.executingServices.size() > 0) {
            // 正在执行Service的回调
            ......
        } else if (app == TOP_APP) {
            // 前台进程,但系统灭屏
            ......
        } else {
            // 空进程
            ......
        }
        
        // 非前台的activities,继续调整Adj
        if (!foregroundActivities && activitiesSize > 0) {
            int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX;
            for (int j = 0; j < activitiesSize; j++) {
                ......
                if (r.visible) {
                    // 如果进程包含可见activity,Adj仅升级调整,升至VISIBLE
                    ......
                } else if (r.isState(ActivityState.PAUSING, ActivityState.PAUSED)) {
                    // 如果进程activity处理暂停状态,Adj升至PERCEPTIBLE
                    ......
                } else if (r.isState(ActivityState.STOPPING)) {
                    // 如果进程activity处理正在停止状态,Adj升至PERCEPTIBLE
                    ......
                } else {
                    // 如果进程只包含cached-activity,仅调整procState
                    ......
                }
            }
            // 非前台包含可见activity进程的Adj跟随层次变化,越往下Adj越大
            if (adj == ProcessList.VISIBLE_APP_ADJ) {
                adj += minLayer;
            }
        }
        ......
        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
            // 对于前台服务进程或显示overylay UI的进程,Adj设置为PERCEPTIBLE,但procState不同
            ......
        }
        
        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
            // 进程显示toasts,Adj升至PERCEPTIBLE
            ......
        }
        
        if (app == mHeavyWeightProcess) {
            // 重量级进程,Adj升至HEAVY_WEIGHT
            ......
        }
        
        if (app == mHomeProcess) {
            // home进程,Adj升至HOME
            ......
        }
        
        if (app == mPreviousProcess && app.activities.size() > 0) {
            // 上一个前台进程,Adj升至PREVIOUS
            ......
        }
        ......
        if (mBackupTarget != null && app == mBackupTarget.app) {
            // 进程正在执行备份时,应该避免被啥掉,Adj升至BACKUP
            ......
        }
        ......
        // 处理service的Adj
        for (int is = app.services.size()-1;
                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                        || procState > ActivityManager.PROCESS_STATE_TOP);
                is--) {
            ServiceRecord s = app.services.valueAt(is);
            if (s.startRequested) {
                // 当进程中含有Unbounded Service时
                ......
            }
            
            for (int conni = s.connections.size()-1;
                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                            || procState > ActivityManager.PROCESS_STATE_TOP);
                    conni--) {
                // 当进程中含有Bounded Service时
                ......
            }
        }

        // 处理含有ContentProvider的进程
        for (int provi = app.pubProviders.size()-1;
                provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                        || procState > ActivityManager.PROCESS_STATE_TOP);
                provi--) {
            ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
            ......
        }
 
        // 如果之前运行ContentProvider存活时间没有超时,Adj升至PREVIOU
        if (app.lastProviderTime > 0 &&
                (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
            ......
        }
        
        // 如果services或providers的客户端处于top状态,进一步处理
        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
            ......
        }
        
        // Cache进程,进一步处理
        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
            ......
        }
        
        // 对service进程做特殊处理
        if (adj == ProcessList.SERVICE_ADJ) {
            ......
        }
        ......
        return app.curAdj < prevAppAdj;
    }

computeOomAdjLocked()的代码量很大,逻辑非常复杂。简单来说就是根据进程的各种状态,来调整Adj、schedGroup、procState等。这里只是简单撸了一下大致的流程,细节没有写,太多了。

applyOomAdj

计算完进程的OOM Adj后,需要通过applyOomAdjLocked()将Adj值设置到Android LowMemoryKiller(LMK)机制中去,具体源码如下。

    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed) {
        ......
        if (app.curAdj != app.setAdj) {
            // 将curAdj设置到LMK系统中,Adj值最终会写入到线程对应的Proc文件中
            ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
            app.setAdj = app.curAdj;
            app.verifiedAdj = ProcessList.INVALID_ADJ;
        }
        
        if (app.setSchedGroup != app.curSchedGroup) {
            int oldSchedGroup = app.setSchedGroup;
            app.setSchedGroup = app.curSchedGroup;
            if (app.waitingToKill != null && app.curReceivers.isEmpty()
                    && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
                // 当进程处于后台等待被杀时,杀掉进程
                app.kill(app.waitingToKill, true);
                success = false;
            } else {
                ......
                try {
                    // 设置整个进程的Group
                    setProcessGroup(app.pid, processGroup);
                    if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
                        // 如果进程的Group从非TOP变为TOP时,提高UI线程和Render线程的调度优先级。
                        // 或者使用RT调度策略,或者在标准调度策略下将优先级设置为-10。
                        ......
                    } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
                               app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                        // 如果进程的Group从TOP变为非TOP时,降低UI线程和Render线程的调度优先级。
                        // 或者将调度策略改为SCHED_OTHER,或者将优先级恢为0。
                        ......
                    } catch (Exception e) {
                    ......
                }
            }
            // 调整上一次上报的前台activities和ProcessState的状态
            ......
            if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT
                || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {
                // 关于内存的进程状态发生变化时更新下次收集PSS数据得到时间
                ......
            } else {
                // 如果定时时间到了,收集PSS数据
                ......
            }
            if (app.setProcState != app.curProcState) {
                // 更新进程状态
                ......
            } else if (app.reportedInteraction && (nowElapsed-app.interactionEventTime)
                > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
                // 长时间处于交互状态的进程,每天至少上报一次使用状态
                maybeUpdateUsageStatsLocked(app, nowElapsed);
            }

            // 进程activities发生改变时需要发送广播,放入待处理队列。
            if (changes != 0) {
                ......
            }

        return success;
    }

applyOomAdjLocked()不仅仅设置了LMK的Adj值,还完成了调整了TOP进程的调度策略或优先级、收集PSS数据、发送状态改变广播等工作。

你可能感兴趣的:(android)