Android Process and Thread Scheduling: OOM Adjustment, Priority Levels, and Scheduling Groups
The article explains Android’s process lifecycle and how component states translate into OOM adjustments, LowMemoryKiller levels, scheduling groups, and Linux thread priorities, detailing the mapping of importance levels to oom_score_adj, schedGroup, procState, and cgroup policies, with code examples and a bug case.
This article continues the series "From Linux Process Scheduling to Android Thread Management" and focuses on Android process lifecycle, OOM adjustment (oom_score_adj), LowMemoryKiller levels, and how the system maps process importance to scheduling groups.
Process Importance Levels
Foreground process
Visible process
Service process
Background process
Empty process
The importance of a process changes through various system events, which are reflected in the /proc/[pid]/oom_score_adj file (range -1000 to +1000). Older kernels used /proc/[pid]/oom_adj (range -17 to +15).
LowMemoryKiller (LMK) Levels
CACHED_APP_MAX_ADJ
CACHED_APP_MIN_ADJ
BACKUP_APP_ADJ
PERCEPTIBLE_APP_ADJ
VISIBLE_APP_ADJ
FOREGROUND_APP_ADJ
LMK kills processes starting from the highest‑adj level when memory is low.
ProcessRecord Fields
int
maxAdj; // Maximum OOM adjustment for this process
int
curRawAdj; // Current OOM unlimited adjustment
int
setRawAdj; // Last set OOM unlimited adjustment
int
curAdj; // Current OOM adjustment
int
setAdj; // Last set OOM adjustment
int
verifiedAdj; // Verified OOM adjustmentProcess State Mapping
ActivityManager defines process_state levels that are kept in sync with oom_score_adj . The article lists the mapping between activity, service, broadcast, and content‑provider events and the resulting schedGroup , adj , and procState values.
Schedule Group Constants (ActivityManager)
static final int SCHED_GROUP_BACKGROUND = 0;
static final int SCHED_GROUP_DEFAULT = 1;
static final int SCHED_GROUP_TOP_APP = 2;
static final int SCHED_GROUP_TOP_APP_BOUND = 3;ProcessRecord also stores curSchedGroup and setSchedGroup to reflect the desired scheduling class.
Priority Changes for Activities, Services, and ContentProviders
The article provides detailed tables that show how different component states (e.g., a visible activity, a foreground service, a heavy‑weight process, a home/launcher process, a recent content‑provider) map to adj , schedGroup , and procState . It also explains the handling of bind‑service flags such as BIND_WAIVE_PRIORITY , BIND_NOT_FOREGROUND , BIND_IMPORTANT , and BIND_ADJUST_WITH_ACTIVITY .
Key code excerpt for service binding:
if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
if ((cr.flags & Context.BIND_NOT_FOREGROUND) == 0) {
if (client.curSchedGroup > schedGroup) {
if ((cr.flags & Context.BIND_IMPORTANT) != 0) {
schedGroup = client.curSchedGroup;
} else {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
}
}
}Similar logic is shown for content‑provider handling and recent‑provider boosting.
Thread Priority Mapping (Java ↔ Linux Nice)
Java thread priorities (1‑10) are mapped to Linux nice values via Thread.setNativePriority . The mapping table is:
Java Priority
Nice Value
1
19
2
16
3
13
4
10
5
0
6
-2
7
-4
8
-5
9
-6
10
-8
Developers can also set thread priority via Process.setThreadPriority(int) , which directly changes the Linux nice value.
Applying OOM Adjustment and Scheduling Changes
After the computeOomAdjLocked calculation (≈700 lines), applyOomAdjLocked updates adj , procState , and schedGroup . The code then maps schedGroup to a Linux thread group ( THREAD_GROUP_BG_NONINTERACTIVE , THREAD_GROUP_TOP_APP , or THREAD_GROUP_DEFAULT ) and updates cgroups and scheduling policies for all tasks of the process.
if (app.setSchedGroup != app.curSchedGroup) {
int oldSchedGroup = app.setSchedGroup;
app.setSchedGroup = app.curSchedGroup;
// setProcessGroup() updates cgroup for every task
setProcessGroup(app.pid, processGroup);
// Adjust UI thread priority when the app becomes top
if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
scheduleAsFifoPriority(app.pid, true);
if (app.renderThreadTid != 0) {
scheduleAsFifoPriority(app.renderThreadTid, true);
}
} else {
setThreadPriority(app.pid, 0); // reset to normal
if (app.renderThreadTid != 0) {
setThreadPriority(app.renderThreadTid, 0);
}
}
}The article concludes with a practical case: a bug where the main thread was unintentionally set to THREAD_PRIORITY_BACKGROUND , causing the UI to run at a low priority and leading to noticeable jank.
Overall, the piece gives a comprehensive view of how Android derives process priority from component lifecycle events, how those priorities are represented in oom_score_adj , and how they are enforced through Linux scheduling groups and cgroups.
iQIYI Technical Product Team
The technical product team of iQIYI
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.