提起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的數量判斷當前內存的緊張程度生成對應等級,根據當前系統內存等級,來要求進程做對應的進程內的回收。
