Android内存管理篇 - 从updateOomAdjLocked看lowmemorykiller之外的Android进程回收机制
提起android的进程回收机制,大家所熟知的是Android的lowmemroykiller的机制。当系统可用内存低于某个阀值时,即会杀死这个阀值对应的Adj值的所有应用。但是本篇文章并为是要介绍Lowmemorykiller的机制,而是要搞清楚在未触发Android低杀机制时,Android是否有某种策略对进程进行回收。因为随着硬件成本低降低,各品牌手机低配置也是越来越高,4G及6G内存的手机层出不穷,在这类高内存配置的手机下,按照lowmemorykiller的默认参数来看,低杀出现的概率也小了很多。通过对AMS中的updateOomAdjLocked()方法的分析即可了解Android在lowmemorykiller之外的Android进程回收机制。
那我们按顺序来介绍updateOomAdjLocked方法。首先时初始化一些变量用以后续的操作。TOP_ACT和TOP_APP表示当前处于前台的app(若无app 处于前台则是TASK栈栈顶的应用);根据LRU列表得到当前系统的存在的进程数量N;重置用户组中的进程状态的动作。TOP_ACT,rankTaskLayersIfNeeded方法,mAdjSeq这些变量主要是用于后面方法中会待调用的computateOomAdjLocked()方法,这不是我们本篇的重点,所以不再展开进行详解。
final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; final long now = SystemClock.uptimeMillis(); final long nowElapsed = SystemClock.elapsedRealtime(); final long oldTime = now - ProcessList.MAX_EMPTY_TIME; final int N = mLruProcesses.size(); ... // Reset state in all uid records. for (int i=mActiveUids.size()-1; i>=0; i--) { final UidRecord uidRec = mActiveUids.valueAt(i); if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); uidRec.reset(); } mStackSupervisor.rankTaskLayersIfNeeded(); mAdjSeq++; mNewNumServiceProcs = 0; mNewNumAServiceProcs = 0;
mProcessLimit表示的是系统允许保留的后台进程和empty进程的总和,初始化为ProcessList.MAX_CACHED_APPS常量,默认为32。Android自身的机制在运行中不会主动修改这个值,但用户可以在“开发者选项”的“后台进程限制”这一项来修改该值。emptyProcessLimit和cachedProcessLimit分别为根据mProcessLimit的值计算出的允许的后台进程和empty进程的最大数量。当mProcessLimit不小于0且不等于1时,后台进程和empty进程和后台进程的数量各占mProcessLimit的一半。 本文讲的后台进程是指cached进程里的非empty的进程,而不包含后台服务进程。
final int emptyProcessLimit; final int cachedProcessLimit; if (mProcessLimit <= 0) { emptyProcessLimit = cachedProcessLimit = 0; } else if (mProcessLimit == 1) { emptyProcessLimit = 1; cachedProcessLimit = 0; } else { emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit); cachedProcessLimit = mProcessLimit - emptyProcessLimit; }
再往下是初始化一些变量的操作,这里要重点注意numSlots所表达的意思。ProcessList.CACHED_APP_MAX_ADJ和Process.CACHED_APP_MIN_ADJ常量系统默认的值分别为906和900,表示的是后台进程和empty进程分配的值是在900至906之间,共有7个值。numSloas计算过程中除以2是因为每个槽配置到进程包含后台进程和empty进程两种,两种进程需用不同adj值表示,所以每个槽包含两个adj值的分配空间,所以需要除以二,计算出来的numSlots值为3。emptyFactor表示每个槽中需要放置几个empty进程,是根据当前empty进程总数决定的,cachedFactor即是表示需要放置几个后台进程到每个槽中。
为便于理解,结合后面代码逻辑举例来讲,比如后台此时有15个cahcedProcess(后台进程)和12个emptyProcess,则会将15/3=5个cachedProcess设置到在第一个槽(可分配900,901这两个oom_adj值)中,oom_adj设置为900;将12/4=4个emptyProcess设置在第一槽,oom_adj值设置为901;然后再设置5个cachedProcess和4个emptyProcess的oom_adj值分别为902和903,即第二个槽。
// Let's determine how many processes we have running vs. // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2; int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; if (numEmptyProcs > cachedProcessLimit) { // If there are more empty processes than our limit on cached // processes, then use the cached process limit for the factor. // This ensures that the really old empty processes get pushed // down to the bottom, so if we are running low on memory we will // have a better chance at keeping around more cached processes // instead of a gazillion empty processes. numEmptyProcs = cachedProcessLimit; } int emptyFactor = numEmptyProcs/numSlots; if (emptyFactor < 1) emptyFactor = 1; int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots; if (cachedFactor < 1) cachedFactor = 1; int stepCached = 0; int stepEmpty = 0; int numCached = 0; int numEmpty = 0; int numTrimming = 0; mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0;
接下来是对mLruProcesses列表进行遍历,通过遍历的过程会更新每一个进程的Adj值。遍历过程中首先会先调用computOomAdjLocked方法进行oom_adj的计算,在该方法中其实已经完成了cachedProcess和emptyProcess进程以外的进程的oom_adj值的计算,而对于未完成计算的这两类进程会进入到下面的if (app.curAdj >= ProcessList.UNKNOWN_ADJ)的逻辑中,该图中已省略去,后文再讲。再往下调用了applyOomAdjLocked的逻辑来更新计算好的oom_adj值。再往下进入switch循环,这个循环里的逻辑即可解答我们在文中开头提出的问题。最后是一个针对isolated进程的特殊的处理。
// First update the OOM adjustment for each of the
// application processes based on their current state.
int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextCachedAdj = curCachedAdj+1;
int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextEmptyAdj = curEmptyAdj+2;
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
app.procStateChanged = false;
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
// If we haven't yet assigned the final cached adj
// to the process, do that now.
if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
...
}
applyOomAdjLocked(app, true, now, nowElapsed);
// Count the number of process types.
switch (app.curProcState) {
...
}
if (app.isolated && app.services.size() <= 0) {
...
}
if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
&& !app.killedByAm) {
numTrimming++;
}
}
}
首先来看if (app.curAdj >= ProcessList.UNKNOWN_ADJ)里的逻辑,即是对cachedProcess和emptyProcess进程的oom_adj计算的过程。当进程为包含activity的cached进程即文中所表达的后台进程或者其客户端进程时,设置该进程的adj值为curCachedAdj,curAdj初始为Process.CACHED_APP_MIN_ADJ,即900。分配的逻辑前文已经提到过,如果时15个CachedProcess,则cachedFactor为15,分配5个cachedProcess进程的oom_adj值为900后,增加curCachedAdj值到902,再分配5个cachedProcess的oom_adj值为902,依次类推到分配完。在该switch的逻辑中default处理的是emptyProcess进程的oom_adj计算方式,与cachedProcess相似,这里不再贴出叙述了。
switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: // This process is a cached process holding activities... // assign it the next cached value for that type, and then // step that cached level. app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + ")"); if (curCachedAdj != nextCachedAdj) { stepCached++; if (stepCached >= cachedFactor) { stepCached = 0; curCachedAdj = nextCachedAdj; nextCachedAdj += 2; if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } break; default: ...//处理empty进程,方式与上面的cachedProcess相似 break; }
再来看switch (app.curProcState)逻辑中的处理,这里完成的主要就是对比当前cachedProcess和emptyProcess的数量是否超过这两类进程的限制值,如果超过了则调用kill方法杀掉该进程。由于这是在便利LruProcesses列表中完成的,遍历过程是由最近的进程到最远的进程的顺序来完成的,所以当遍历到越久远到进程时numCached的值越大,则越容易超过限制值。
由此,我们可以了解到Android系统在Lowmemeorykill之外的回收机制是:
1.只会对于后台进程和空进程进行回收
2.后台进程和空进程的系统允许的最大限制值为ProcessList.MAX_CACHED_APPS常量的一半
3.当后台进程数或空进程数超过限制值时会对最久不使用的进程进行回收(调用kill方法),直到这两类进程数量不多于各自的进程限制值。
// Count the number of process types. switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: mNumCachedHiddenProcs++; numCached++; if (numCached > cachedProcessLimit) { app.kill("cached #" + numCached, true); } break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: if (numEmpty > ProcessList.TRIM_EMPTY_APPS && app.lastActivityTime < oldTime) { app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) / 1000) + "s", true); } else { numEmpty++; if (numEmpty > emptyProcessLimit) { app.kill("empty #" + numEmpty, true); } } break; default: mNumNonCachedProcs++; break; }
在LruProcess遍历的最后是对isolated process单独的处理,对于设置了isolated属性的进程为true的进程如果已经不包含服务了,则立刻kill掉该进程,最后是记录当前adj值大于等于桌面进程的所有进程数量,保存到numTrimming中。
if (app.isolated && app.services.size() <= 0) { // If this is an isolated process, and there are no // services running in it, then the process is no longer // needed. We agressively kill these because we can by // definition not re-use the same process again, and it is // good to avoid having whatever code was running in them // left sitting around after no longer needed. app.kill("isolated not needed", true); } else { // Keeping this process, update its uid. final UidRecord uidRec = app.uidRecord; if (uidRec != null && uidRec.curProcState > app.curProcState) { uidRec.curProcState = app.curProcState; } } if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.killedByAm) { numTrimming++; }
虽然已经了解完android在非lowmemory情况下的内存回收机制,我们继续来看完updateOomAdjLocked方法在更新完adj值后续的动作。首先根据当前的cachedProcess和emptyProcess进程的数量来综合判定当前进程的等级,这两类进程的数量越少,表示系统内存越紧张,内存等级越高。由低至高(对应的数值也是由低至高)分别为ADJ_MEM_FACTOR_NORMAL、ADJ_MEM_FACTOR_MODERATE、ADJ_MEM_FACTOR_LOW、ADJ_MEM_FACTOR_CRITICAL这四个等级。
为什么能根据后台进程和空进程数量来判断出系统的内存等级呢?因为根据之前的分析可以知道,Android系统在后台进程和空进程不超过数量上限时总是尽可能多的保留后台进程和空进程,这样用户便可再再次启动这些进程时减少启动时间从而提高了用户体验;而lowmemeorykiller的机制又会在系统可用内存不足时杀死这些进程,所以在后台进程和空进程数量少于一定数量时,便表示了系统以及触发了lowmemrorykiller的机制,而剩余的后台进程和空进程的数量则正好体现了Lowmemroykiller杀进程的程度,即表示当前系统内存的紧张程度。
// Now determine the memory trimming level of background processes. // Unfortunately we need to start at the back of the list to do this // properly. We only do this if the number of background apps we // are managing to keep around is less than half the maximum we desire; // if we are keeping a good number around, we'll let them use whatever // memory they want. final int numCachedAndEmpty = numCached + numEmpty; int memFactor; if (numCached <= ProcessList.TRIM_CACHED_APPS && numEmpty <= ProcessList.TRIM_EMPTY_APPS) { if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; } } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; } // We always allow the memory level to go up (better). We only allow it to go // down if we are in a state where that is allowed, *and* the total number of processes // has gone down since last time. if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel + " numProcs=" + mLruProcesses.size() + " last=" + mLastNumProcesses); if (memFactor > mLastMemoryLevel) { if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) { memFactor = mLastMemoryLevel; if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!"); } }
接下来是系统内存等级不为ADJ_MEM_FACTOR_NORMAL时的处理,此时所有的进程都需要进行内存回收操作,后面的处理就是针对不同的进程得到对应的进程内存回收等级,并按照该等级执行进程内的内存回收操作。判定完进程的回收等级之后,计算出factor变量。这里的factor的计算方式与之前计算oom_adj时的槽的方式相似。则后面遍历时前四个进程属于同一个槽使用同一等级进行回收,之后四个再使用下一个等级进行回收。
mLastMemoryLevel = memFactor; mLastNumProcesses = mLruProcesses.size(); 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; switch (memFactor) { case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; break; case ProcessStats.ADJ_MEM_FACTOR_LOW: fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; break; default: fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; break; } int factor = numTrimming/3; int minFactor = 2; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; for (int i=N-1; i>=0; i--) {
...
} }
展开for (int i=N-1; i>=0; i--)循环,里面是对mLruProcesses进程遍历,顺序是从最近的进程向最久远的进程进行遍历。
首先是遍历过程中重要性低于ActivityManager.PROCESS_STATE_HOME的进程的处理,包括B-Service进程、cachedProcess和emptyProcess。由于curLevel设置的初始值为最高等级,而在遍历mLruProcess过程中curLevel会逐渐减少,结合遍历是由近至远这里涵盖的思想就是越近的进程adj值会越高(相同类别),而adj值越高的进程越不容易被lowmemroykiller机制杀死,这样这类越不容易杀死的进程就越应该执行高回收等级的回收动作。
for (int i=N-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); if (allChanged || app.procStateChanged) { setProcessTrackerStateLocked(app, trackerMemFactor, now); app.procStateChanged = false; } if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.killedByAm) { if (app.trimMemoryLevel < curLevel && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of " + app.processName + " to " + curLevel); app.thread.scheduleTrimMemory(curLevel); } catch (RemoteException e) { } if (false) { // For now we won't do this; our memory trimming seems // to be good enough at this point that destroying // activities causes more harm than good. if (curLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE && app != mHomeProcess && app != mPreviousProcess) { // Need to do this on its own message because the stack may not // be in a consistent state at this point. // For these apps we will also finish their activities // to help them free memory. mStackSupervisor.scheduleDestroyAllActivities(app, "trim"); } } } app.trimMemoryLevel = curLevel; step++; if (step >= factor) { step = 0; switch (curLevel) { case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE; break; case ComponentCallbacks2.TRIM_MEMORY_MODERATE: curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; break; } } } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { ... } else { ... } }
之后是对重要性大于PROCESS_STATE_HOME的PROCESS_STATE_HEAVY_WEIGHT进程的处理,只有当进程本身的回收等级低于TRIM_MEMORY_BACKGROUND时,才再执行TRIM_MEMORY_BACKGROUND的等级的回收
else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of heavy-weight " + app.processName + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); app.thread.scheduleTrimMemory( ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); } catch (RemoteException e) { } } app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; }
对于重要性高于PROCESS_STATE_HOME的其他类别的进程,其中如果重要性低于PROCESS_STATE_IMPORTANT_BACKGROUND则以TRIM_MEMORY_UI_HIDDEN的等级进行进程内的回收;如果进程自身的回收等级低于当前回收等级fgTrimLevel,则对该进程以当前回收等级进行回收。这里的意思是,避免多次执行回收,这类重要性高于HOME的进程,只有在内存变得更紧时,才以更高的等级进行回收一次。
else { if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND || app.systemNoUi) && app.pendingUiClean) { // If this application is now in the background and it // had done UI, then give it the special trim level to // have it free UI resources. final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; if (app.trimMemoryLevel < level && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " + app.processName + " to " + level); app.thread.scheduleTrimMemory(level); } catch (RemoteException e) { } } app.pendingUiClean = false; } if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName + " to " + fgTrimLevel); app.thread.scheduleTrimMemory(fgTrimLevel); } catch (RemoteException e) { } } app.trimMemoryLevel = fgTrimLevel; }
再来看,如果根据cachedProcess和emptyProcess数量计算出的系统内存等级为正常时候的处理。简单来说,此时只需要让重要性低于PROCESS_STATE_IMPORTANT_BACKGROUND以TRIM_MEMORY_UI_HIDDEN进行回收即可。
if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { ... } 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; } if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND || app.systemNoUi) && app.pendingUiClean) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of ui hidden " + app.processName + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); app.thread.scheduleTrimMemory( ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); } catch (RemoteException e) { } } app.pendingUiClean = false; } app.trimMemoryLevel = 0; } }
方法的最后是一些扫尾和保存相关状态变化的工作,比如配合开发者模式的选项判断时候执行activity等,这里不再展开介绍。
总结一下,updateOomAdjLocked方法主要完成了三个动作:
一、更新所有应用等oom_adj值,其中非cachedProcess和emptyProcess进程等oom_adj值的计算通过调用computeOomAdjLocked方法完成,而cachedProcess和emptyProcess进程则按照由近至远的方式交替从小至大的分配oom_adj值
二、根据cachedPrcess和emptyProcess的数量判断是否超过数量上限,超过则杀死进程,越久远的进程在超过时优先被杀死,这也回答了我们本文开头提出的问题。
三、根据cachedPrcess和emptyProcess的数量判断当前内存的紧张程度生成对应等级,根据当前系统内存等级,来要求进程做对应的进程内的回收。