之前有写过文章分析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数据、发送状态改变广播等工作。