Mobile Development 23 min read

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.

iQIYI Technical Product Team
iQIYI Technical Product Team
iQIYI Technical Product Team
Android Process and Thread Scheduling: OOM Adjustment, Priority Levels, and Scheduling Groups

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 adjustment

Process 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.

AndroidLowMemoryKillerOOM Adjustmentprocess schedulingThread Priority
iQIYI Technical Product Team
Written by

iQIYI Technical Product Team

The technical product team of iQIYI

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.