Lab 1 — Processes
Read the process table, find every process's UID and parent, watch Zygote fork an app, and use oom_adj to see which processes the system would kill first.
A process list is the cheapest, most informative view of a running Android system. In one screen it shows you the boot sequence's results, the app sandbox, and the hierarchy that Zygote created. This lab teaches you to read it.
TL;DR#
ps -Alists every process with PID, PPID, user and name.- Every app has its own UID, and the UID is how the kernel enforces the sandbox.
- App processes are children of Zygote, which is why their parent is
zygote64rather thaninit. oom_adj(and its modern replacement, the OOM score) is the system's kill priority. Higher means more killable.dumpsys activity processesgives the same information with the framework's own labels.
Mental model#
Think of a theatre company.
The stage manager is system_server: it decides who goes on, in what order, and who gets
pushed off when the stage is crowded.
The understudies are cached app processes: not currently on stage, but kept warm and ready.
The dressing rooms are the app sandboxes: each performer has a private room with a lock, and the stage manager can knock but not enter.
The fire marshal is lmkd: when the building is over capacity, it decides who leaves. It uses
the same priority list the stage manager publishes, which is oom_adj.
Where the analogy breaks#
A theatre company has one stage. Android has one process per app and can keep dozens alive at once, all apparently running. The competition is not for a stage but for RAM, and the decision to evict is made continuously rather than at scene changes.
Deep dive#
The process table#
adb shell ps -AThe columns are user, PID, PPID, VSZ, RSS, WCHAN, ADDR, S, NAME. The three that matter for this
lab are USER, PID and PPID.
Things to look for:
initat PID 1. Every other process descends from it, directly or indirectly.zygote64andzygote. The 64-bit and 32-bit zygotes, started by init.system_server. A child of zygote, not of init. This is the fork model in one line.- App processes. Named after the package, with a UID in the
u0_a...range. - Native services.
surfaceflinger,audioserver,media.*,netd,vold. Most are children of init.
UIDs and the sandbox#
Each installed app gets a UID at install time. The UID is not just a name; it is what the kernel uses to enforce file permissions, and what SELinux uses as one input to its decisions.
adb shell dumpsys package com.android.settings | grep userIdThe output includes a userId= line. Compare it to the USER column in ps for that process: they
match. That is the sandbox in two commands.
A few UIDs are fixed and well known: root is 0, system is 1000, shell is 2000. App UIDs are
allocated from a range and differ per device, so do not hard-code them.
oom_adj and the kill order#
oom_adj is a per-process score that tells the low-memory killer how important the process is.
Lower (more negative) means more important. The framework computes it and writes it to
/proc/<pid>/oom_score_adj; lmkd reads the pressure and kills in that order.
adb shell cat /proc/<pid>/oom_score_adjThe exact values depend on the platform version and the process's state. The useful exercise is relative: compare a foreground app, a visible app, a cached app and a persistent service.
The framework's view#
ps shows the kernel's view. The framework has a richer one:
adb shell dumpsys activity processesThis prints process records with their importance, their current state and their components. The
output is long; look for the ProcessRecord entries and the oom adj lines.
Two other useful views:
adb shell dumpsys activity activities
adb shell dumpsys activity servicesThe first shows the activity stack, the second the running services. Together with processes, they
are the framework's answer to "what is running and why".
Walkthrough#
- Count the processes
adb shell ps -A | wc -lYou should seeA number in the low hundreds on a typical phone. - Find the zygotes and system_server
adb shell ps -A | grep -E "zygote|system_server"You should seezygote64, possibly zygote, and system_server with PPID pointing at zygote. - Find an app process and its UID
adb shell ps -A | grep com.android.settingsYou should seeA line whose USER is u0_a... and whose NAME is the package. - Compare the UID with the package record
adb shell dumpsys package com.android.settings | grep userIdYou should seeThe userId in the dump matches the USER column. - Read the framework's process list
adb shell dumpsys activity processes | head -80You should seeProcessRecord entries with importance and oom adj values.
adb shell ps -A
adb shell ps -A | grep -E "zygote|system_server"
adb shell ps -A | grep com.android.settings
adb shell dumpsys package com.android.settings | grep userId
adb shell dumpsys activity processes | head -80Example output
Illustrative output — process names and UIDs differ per device.Challenges#
Challenge 1 — Prove that system_server is forked from Zygote#
Use ps to show that system_server's parent is a zygote process, not init.
Reveal the answer
adb shell ps -A | grep -E "zygote|system_server"Find the system_server line and look at its PPID. Then find the process with that PID: it is
zygote64 (or zygote). init is PID 1, and the zygote is its child, but system_server is a
child of the zygote because it was forked from the preloaded template.
Challenge 2 — Rank three processes by kill priority#
Pick a foreground app, a cached app, and a persistent system process. Read their
/proc/<pid>/oom_score_adj values and put them in kill order.
Reveal the answer
adb shell ps -A | grep <package>
adb shell cat /proc/<pid>/oom_score_adjLower values are more important. A persistent system process will have the lowest (most negative) value, a foreground app a moderately low one, and a cached app a positive or near-zero one. The killer evicts from the highest value downward. The exact numbers vary by version; the ordering is the answer.
Challenge 3 — Find a process that is not an app#
List processes whose UID is system rather than an app UID.
Reveal the answer
adb shell ps -A | grep " system "You will see native services and some framework processes. Compare their PPIDs: most are children
of init, because they were started by init from init.rc rather than forked from Zygote. That
difference is the boundary between the native layer and the app model.
What you learned#
You can now read the process table as a diagram of the boot sequence and the sandbox. The next lab looks at the services that run inside the framework process.
Next#
Lab 2 — Services goes inside system_server and the service registry.
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsAndroid Debug Bridge (adb)developer.android.comaccessed 2026-10-27↩
- docsProcesses and threads overview | App qualitydeveloper.android.comaccessed 2026-10-27↩
- docsdumpsysdeveloper.android.comaccessed 2026-10-27↩
- docsZygotesource.android.comaccessed 2026-10-27↩
- docsManage your app's memory overviewdeveloper.android.comaccessed 2026-10-27↩
- docsLow-memory killer daemon (lmkd)source.android.comaccessed 2026-10-27↩