Every running Android process is assigned an importance score (oom_adj) based on what the user is doing with it right now: a Foreground app is adj 0, a Visible-but-not-focused app is adj 100, a Service running work is adj 500, a cached Background app the user switched away from is adj 900, and an Empty process holding no components is adj 1000 — least important, evicted first.
The Low-Memory Killer (lmkd) watches free RAM against a rising staircase of thresholds, one per tier:
free(t) = totalRAM − reservedOS − Σ mem(alive processes)
for tier in [Empty, Background, Service, Visible, Foreground]:
if free < minfree[tier] and any process in tier:
kill the largest process in that tier
recompute free
Because minfree[Empty] is the highest threshold and minfree[Foreground] the lowest, Empty apps get reaped while there is still plenty of free RAM, and Foreground apps are only ever killed in a genuine near-OOM crisis. This is the real mechanism behind "why did my background app restart from scratch" on Android.
- Total RAM — device memory; minfree thresholds scale proportionally with it, exactly like Android's per-device lowmemorykiller tuning.
- Memory growth rate — every alive process leaks/grows at this rate, driving free memory down over time so the killer has to act.
- Launch App — starts a new Foreground process and demotes the previous foreground app to Visible; apps left untouched age Visible → Service → Background → Empty over time, exactly like real activity lifecycle transitions.
- Click a process card — brings that app back to the Foreground (a real "user re-opened the app" event), rescuing it from the eviction queue.