From ee7621c0f5de6eca2cfb9fb2b6117fb61e13cc41 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 13 Aug 2012 16:42:18 -0700 Subject: [PATCH] Modify how the background process LRU list is handled. A long time ago, we had a concept of an "empty" process -- this was a process that didn't have any interesting components in it, which would be placed below everything else in the LRU list. Empty processes didn't work out well, because you could get into bad situations where you have filled your LRU list with things that have hidden activities, pushing empty processes to the bottom and being immediately killed as soon as they go into the list. So this was removed. This change brings the concept back, but in a slightly different form, to address a more specific problem: for people who are switching between N different applications, we would like to try to keep those activities available in RAM in a consistent manner. Currently the previous activities would be killed often quickly and suprisingly, even on devices with lots of RAM. This is for two reasons: (1) As you sit in one application, other things going on in the background will go to the top of the LRU list, pushing down the previous apps you have visited, even though you aren't aware at all of these other things executing. (2) There is a hard limit on the number of background processes (currently 16) after which they are killed regardless of the amount of available RAM. This is desireable because if there is lots of RAM we can end up with tons and tons of processes sitting around, not really serving any purpose, but using up resources. To improve the situation, we have again a concept of "empty" processes but now it means one with no activities. Processes that aren't empty but in the background list are called hidden. We maintain these as two parallel lists, each getting half of the process limit: so with a 16 process limit, you can have at most 8 empty and 8 hidden processes. This allows us to consistently keep up to 8 recent applications around for fast app switching; we will also keep around 8 other processes to make it more efficient for background work to execute again if it needs to. Change-Id: Iee06e45efc20787da6a1e50020e5421c28204bd7 --- .../server/am/ActivityManagerService.java | 186 +++++++++++++----- .../com/android/server/am/ProcessRecord.java | 9 +- 2 files changed, 147 insertions(+), 48 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d3ec9f7b8b3df..4a8160a04fb73 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -685,6 +685,18 @@ public final class ActivityManagerService extends ActivityManagerNative */ int mLruSeq = 0; + /** + * Keep track of the non-hidden/empty process we last found, to help + * determine how to distribute hidden/empty processes next time. + */ + int mNumNonHiddenProcs = 0; + + /** + * Keep track of the number of hidden procs, to balance oom adj + * distribution between those and empty procs. + */ + int mNumHiddenProcs = 0; + /** * Keep track of the number of service processes we last found, to * determine on the next iteration which should be B services. @@ -9081,7 +9093,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mGoingToSleep=" + mMainStack.mGoingToSleep); pw.println(" mLaunchingActivity=" + mMainStack.mLaunchingActivity); pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); - pw.println(" mNumServiceProcs=" + mNumServiceProcs + pw.println(" mNumNonHiddenProcs=" + mNumNonHiddenProcs + + " mNumHiddenProcs=" + mNumHiddenProcs + + " mNumServiceProcs=" + mNumServiceProcs + " mNewNumServiceProcs=" + mNewNumServiceProcs); } @@ -9746,6 +9760,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(" "); pw.print("oom: max="); pw.print(r.maxAdj); pw.print(" hidden="); pw.print(r.hiddenAdj); + pw.print(" empty="); pw.print(r.emptyAdj); pw.print(" curRaw="); pw.print(r.curRawAdj); pw.print(" setRaw="); pw.print(r.setRawAdj); pw.print(" cur="); pw.print(r.curAdj); @@ -11966,14 +11981,15 @@ public final class ActivityManagerService extends ActivityManagerNative } private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, - ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { + int emptyAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { if (mAdjSeq == app.adjSeq) { // This adjustment has already been computed. If we are calling // from the top, we may have already computed our adjustment with // an earlier hidden adjustment that isn't really for us... if // so, use the new hidden adjustment. if (!recursed && app.hidden) { - app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj; + app.curAdj = app.curRawAdj = app.nonStoppingAdj = + app.hasActivities ? hiddenAdj : emptyAdj; } return app.curRawAdj; } @@ -11981,7 +11997,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.thread == null) { app.adjSeq = mAdjSeq; app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - return (app.curAdj=ProcessList.HIDDEN_APP_MAX_ADJ); + return (app.curAdj=app.curRawAdj=ProcessList.HIDDEN_APP_MAX_ADJ); } app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; @@ -11998,6 +12014,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.nonStoppingAdj = app.maxAdj; + app.hasActivities = false; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; @@ -12008,12 +12025,15 @@ public final class ActivityManagerService extends ActivityManagerNative app.systemNoUi = true; if (app == TOP_APP) { app.systemNoUi = false; + app.hasActivities = true; } else if (activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { app.systemNoUi = false; - break; + } + if (r.app == app) { + app.hasActivities = true; } } } @@ -12022,6 +12042,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.keeping = false; app.systemNoUi = false; + app.hasActivities = false; // Determine the importance of the process, starting with most // important to least, and assign an appropriate OOM adjustment. @@ -12037,6 +12058,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "top-activity"; foregroundActivities = true; interesting = true; + app.hasActivities = true; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -12058,21 +12080,13 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "exec-service"; - } else if (activitiesSize > 0) { - // This app is in the background with paused activities. - // We inspect activities to potentially upgrade adjustment further below. + } else { + // Assume process is hidden (has activities); we will correct + // later if this is not the case. adj = hiddenAdj; schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; app.hidden = true; app.adjType = "bg-activities"; - } else { - // A very not-needed process. If this is lower in the lru list, - // we will push it in to the empty bucket. - adj = hiddenAdj; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = true; - app.empty = true; - app.adjType = "bg-empty"; } boolean hasStoppingActivities = false; @@ -12089,6 +12103,7 @@ public final class ActivityManagerService extends ActivityManagerNative } schedGroup = Process.THREAD_GROUP_DEFAULT; app.hidden = false; + app.hasActivities = true; foregroundActivities = true; break; } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { @@ -12106,9 +12121,20 @@ public final class ActivityManagerService extends ActivityManagerNative foregroundActivities = true; hasStoppingActivities = true; } + if (r.app == app) { + app.hasActivities = true; + } } } + if (adj == hiddenAdj && !app.hasActivities) { + // Whoops, this process is completely empty as far as we know + // at this point. + adj = emptyAdj; + app.empty = true; + app.adjType = "bg-empty"; + } + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { if (app.foregroundServices) { // The user is aware of this app, so make it visible. @@ -12242,8 +12268,16 @@ public final class ActivityManagerService extends ActivityManagerNative myHiddenAdj = ProcessList.VISIBLE_APP_ADJ; } } - clientAdj = computeOomAdjLocked( - client, myHiddenAdj, TOP_APP, true, doingAll); + int myEmptyAdj = emptyAdj; + if (myEmptyAdj > client.emptyAdj) { + if (client.emptyAdj >= ProcessList.VISIBLE_APP_ADJ) { + myEmptyAdj = client.emptyAdj; + } else { + myEmptyAdj = ProcessList.VISIBLE_APP_ADJ; + } + } + clientAdj = computeOomAdjLocked(client, myHiddenAdj, + myEmptyAdj, TOP_APP, true, doingAll); String adjType = null; if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { // Not doing bind OOM management, so treat @@ -12382,8 +12416,16 @@ public final class ActivityManagerService extends ActivityManagerNative myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ; } } - int clientAdj = computeOomAdjLocked( - client, myHiddenAdj, TOP_APP, true, doingAll); + int myEmptyAdj = emptyAdj; + if (myEmptyAdj > client.emptyAdj) { + if (client.emptyAdj > ProcessList.FOREGROUND_APP_ADJ) { + myEmptyAdj = client.emptyAdj; + } else { + myEmptyAdj = ProcessList.FOREGROUND_APP_ADJ; + } + } + int clientAdj = computeOomAdjLocked(client, myHiddenAdj, + myEmptyAdj, TOP_APP, true, doingAll); if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { @@ -12796,9 +12838,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final boolean updateOomAdjLocked( - ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP, boolean doingAll) { + private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj, + int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) { app.hiddenAdj = hiddenAdj; + app.emptyAdj = emptyAdj; if (app.thread == null) { return false; @@ -12808,7 +12851,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean success = true; - computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll); + computeOomAdjLocked(app, hiddenAdj, emptyAdj, TOP_APP, false, doingAll); if (app.curRawAdj != app.setRawAdj) { if (wasKeeping && !app.keeping) { @@ -12895,7 +12938,8 @@ public final class ActivityManagerService extends ActivityManagerNative mAdjSeq++; - boolean success = updateOomAdjLocked(app, app.hiddenAdj, TOP_APP, false); + boolean success = updateOomAdjLocked(app, app.hiddenAdj, app.emptyAdj, + TOP_APP, false); final boolean nowHidden = app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ && app.curAdj <= ProcessList.HIDDEN_APP_MAX_ADJ; if (nowHidden != wasHidden) { @@ -12923,34 +12967,53 @@ public final class ActivityManagerService extends ActivityManagerNative // 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.HIDDEN_APP_MAX_ADJ - ProcessList.HIDDEN_APP_MIN_ADJ + 1; - int factor = (mLruProcesses.size()-4)/numSlots; - if (factor < 1) factor = 1; - int step = 0; + int numSlots = (ProcessList.HIDDEN_APP_MAX_ADJ + - ProcessList.HIDDEN_APP_MIN_ADJ + 1) / 2; + int emptyFactor = (mLruProcesses.size()-mNumNonHiddenProcs-mNumHiddenProcs)/numSlots; + if (emptyFactor < 1) emptyFactor = 1; + int hiddenFactor = (mNumHiddenProcs > 0 ? mNumHiddenProcs : 1)/numSlots; + if (hiddenFactor < 1) hiddenFactor = 1; + int stepHidden = 0; + int stepEmpty = 0; + final int emptyProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit; + final int hiddenProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit; int numHidden = 0; + int numEmpty = 0; int numTrimming = 0; - + + mNumNonHiddenProcs = 0; + mNumHiddenProcs = 0; + // First update the OOM adjustment for each of the // application processes based on their current state. int i = mLruProcesses.size(); int curHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + int nextHiddenAdj = curHiddenAdj+1; + int curEmptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + int nextEmptyAdj = curEmptyAdj+2; while (i > 0) { i--; ProcessRecord app = mLruProcesses.get(i); //Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj); - updateOomAdjLocked(app, curHiddenAdj, TOP_APP, true); - if (curHiddenAdj < ProcessList.HIDDEN_APP_MAX_ADJ - && app.curAdj == curHiddenAdj) { - step++; - if (step >= factor) { - step = 0; - curHiddenAdj++; - } - } + updateOomAdjLocked(app, curHiddenAdj, curEmptyAdj, TOP_APP, true); if (!app.killedBackground) { - if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + if (app.curRawAdj == curHiddenAdj && app.hasActivities) { + // This process was assigned as a hidden process... step the + // hidden level. + mNumHiddenProcs++; + if (curHiddenAdj != nextHiddenAdj) { + stepHidden++; + if (stepHidden >= hiddenFactor) { + stepHidden = 0; + curHiddenAdj = nextHiddenAdj; + nextHiddenAdj += 2; + if (nextHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { + nextHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + } + } + } numHidden++; - if (numHidden > mProcessLimit) { + if (numHidden > hiddenProcessLimit) { Slog.i(TAG, "No longer want " + app.processName + " (pid " + app.pid + "): hidden #" + numHidden); EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, @@ -12958,8 +13021,37 @@ public final class ActivityManagerService extends ActivityManagerNative app.killedBackground = true; Process.killProcessQuiet(app.pid); } + } else { + if (app.curRawAdj == curEmptyAdj || app.curRawAdj == curHiddenAdj) { + // This process was assigned as an empty process... step the + // empty level. + if (curEmptyAdj != nextEmptyAdj) { + stepEmpty++; + if (stepEmpty >= emptyFactor) { + stepEmpty = 0; + curEmptyAdj = nextEmptyAdj; + nextEmptyAdj += 2; + if (nextEmptyAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { + nextEmptyAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + } + } + } + } else if (app.curRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) { + mNumNonHiddenProcs++; + } + if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + numEmpty++; + if (numEmpty > emptyProcessLimit) { + Slog.i(TAG, "No longer want " + app.processName + + " (pid " + app.pid + "): empty #" + numEmpty); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "too many background"); + app.killedBackground = true; + Process.killProcessQuiet(app.pid); + } + } } - if (!app.killedBackground && app.isolated && app.services.size() <= 0) { + 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 @@ -12989,18 +13081,20 @@ public final class ActivityManagerService extends ActivityManagerNative // 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. - if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) { + if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/4) + && numEmpty <= (ProcessList.MAX_HIDDEN_APPS/4)) { + final int numHiddenAndEmpty = numHidden + numEmpty; final int N = mLruProcesses.size(); - factor = numTrimming/3; + int factor = numTrimming/3; int minFactor = 2; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; - step = 0; + int step = 0; int fgTrimLevel; - if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/5)) { + if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/5)) { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; - } else if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/3)) { + } else if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/3)) { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; } else { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index cba94801c49cd..42b770894ad84 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -61,6 +61,7 @@ class ProcessRecord { long lruWeight; // Weight for ordering in LRU list int maxAdj; // Maximum OOM adjustment for this process int hiddenAdj; // If hidden, this is the adjustment to use + int emptyAdj; // If empty, this is the adjustment to use int curRawAdj; // Current OOM unlimited adjustment for this process int setRawAdj; // Last set OOM unlimited adjustment for this process int nonStoppingAdj; // Adjustment not counting any stopping activities @@ -73,6 +74,7 @@ class ProcessRecord { boolean serviceb; // Process currently is on the service B list boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? + boolean hasActivities; // Are there any activities running in this process? boolean foregroundServices; // Running any services that are foreground? boolean foregroundActivities; // Running any activities that are foreground? boolean systemNoUi; // This is a system process, but not currently showing UI. @@ -199,6 +201,7 @@ class ProcessRecord { pw.print(" empty="); pw.println(empty); pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj); pw.print(" hidden="); pw.print(hiddenAdj); + pw.print(" empty="); pw.print(emptyAdj); pw.print(" curRaw="); pw.print(curRawAdj); pw.print(" setRaw="); pw.print(setRawAdj); pw.print(" nonStopping="); pw.print(nonStoppingAdj); @@ -215,7 +218,9 @@ class ProcessRecord { pw.print(" foregroundServices="); pw.print(foregroundServices); pw.print(" forcingToForeground="); pw.println(forcingToForeground); pw.print(prefix); pw.print("persistent="); pw.print(persistent); - pw.print(" removed="); pw.println(removed); + pw.print(" removed="); pw.print(removed); + pw.print(" hasActivities="); pw.print(hasActivities); + pw.print(" foregroundActivities="); pw.println(foregroundActivities); pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); pw.print(" lruSeq="); pw.println(lruSeq); if (!keeping) { @@ -313,7 +318,7 @@ class ProcessRecord { pkgList.add(_info.packageName); thread = _thread; maxAdj = ProcessList.HIDDEN_APP_MAX_ADJ; - hiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + hiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; curRawAdj = setRawAdj = -100; curAdj = setAdj = -100; persistent = false;