The Android OS runs with limited hardware resources, i.e. CPU/RAM/Power. To strive for the better performance, Oom Ajuster is introduced to tweak the following 3 major factors:
ActivityManager#PROCESS_STATE_*
ProcessList#*_ADJ
Besides the above 3 major factors, Android R introduced the Process Capabilities ActivityManager#PROCESS_CAPABILITY_*
. It's a new attribute to process record, mainly designed for supporting the “while-in-use” permission model - in additional to the traditional Android permissions, wheather or not a process has access to a given API, will be guarded by its current process state as well. The OomAdjuster will compute the process capabilities during updating the oom adj. Meanwhile, the flag ActivityManager#BIND_INCLUDE_CAPABILITIES
enables to possiblity to “transfer” the capability from a client process to the service process it binds to.
System server keeps a list of recent used app processes. Given the 4 types of entities that an Android processes could have: Activity, Service, Content Provider and Broadcast Receiver, the System Server has to adjust the above 3 factors to give the users the best performance according to the states of the entities. A typical case would be that: foreground app A binds into a background service B in order to serve the user, in the case of memory pressure, the background service B should be avoided from being expunged since it would result user-perceptible interruption of service. The Oom Adjuster is to tweak the aforementioned 3 factors for those app processes.
The timing of updating the Oom Adj score is vital: assume a camera process in background gets launched into foreground, launching camera typically incurs high memory pressure, which could incur low memory kills - if the camera process isn't moved out of the background adj group, it could get killed by lmkd. Therefore the updates have to be called pretty frequently: in case there is an activity start, service binding, etc.
The update procedure basically consists of 3 parts:
cached
Oom Adj scores are grouped in bucket
, which is used in the isolated processes: they could be correlated - assume one isolated Chrome process is at Oom Adj score 920 and another one is 980; the later one could get expunged much earlier than the former one, which doesn't make sense; grouping them would be a big relief for this case.PROCESS_STATE_CACHED_EMPTY
, which is the lowest importance.ProcessList#FOREGROUND_APP_ADJ
, meaning it’s propbably a persistent process, there is no too much to do here.BIND_WAIVE_PRIORITY
not set | BIND_ALLOW_OOM_MANAGEMENT
set | Shown UI && Not Home | | Use the app‘s own Adj | | | | Inactive for a while | | Use the app’s own Adj | | | Client has a higher importance | Shown UI && Not Home && client is invisible | | Use the app‘s own Adj | | | | BIND_ABOVE_CLIENT
and BIND_IMPORTANT
set | Client is not persistent | Try client’s Adj | | | | | Client is persistent | Try persistent Adj | | | | BIND_NOT_PERCEPTIBLE
set | client < perceptible && app > low perceptible | Try low perceptible Adj | | | | BIND_NOT_VISIBLE
set | client < perceptible && app > perceptible | Try perceptible Adj | | | | Client >= perceptible | | Try client‘s Adj | | | | Adj > visible | | Max of client/Own Adj | | | | | | Use the app’s own Adj | | | BIND_NOT_FOREGROUND
+BIND_IMPORTANT_BACKGROUND
not set | Client‘s sched group > app’s | BIND_IMPORTANT
is set | Use client‘s sched group | | | | | | Use default sched group | | | | Client’s process state < top | BIND_FOREGROUND_SERVICE
is set | ProcState = bound fg | | | | | BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+ screen ON | ProcState = bound fg | | | | | | ProcState = important fg | | | | Client‘s process state = top | | ProcState = bound top | | | BIND_IMPORTANT_BACKGROUND
not set | Client’s process state < transient bg | | ProcState = transient bg | | | BIND_NOT_FOREGROUND
or BIND_IMPORTANT_BACKGROUND
set | Client's process state < important bg | | ProcState = important bg | | BIND_ADJUST_WITH_ACTIVITY
set | Adj > fg && App visible | | | Adj = foreground | | | | BIND_NOT_FOREGROUND
not set | BIND_IMPORTANT
is set | Sched = top app bound | | | | | BIND_IMPORTANT
is NOT set | Sched = default |Another interesting aspect of the Oom Adjuster is the cycles of the dependencies. A simple example would be like below illustration, process A is hosting a service which is bound by process B; meanwhile the process B is hosting a service which is bound by process A.
There could be very complicated cases, which could involve multiple cycles, and in the dependency graph, each of the process record node could have different importance.
The Oom Adjuster maintains a global sequence ID mAdjSeq
to track the current Oom Adjuster calling. And each of the process record has a field to track in which sequence the process record is evaluated. If during the Oom Adj computation, a process record with sequence ID as same as the current global sequence ID, this would mean that a cycle is detected; in this case: