Overview
Android is a memory-constrained, multi-app environment. The OS must kill processes to reclaim memory for the foreground app. Understanding how process priorities are calculated — and how your app can respond — is essential for writing apps that survive background pressure without leaking memory.
Process Priority Levels
Android assigns every app process a priority level that the Low Memory Killer (LMKD) uses to decide which process to kill first when memory is scarce.
1. Foreground Process
The highest priority. A process is foreground if it has:
- An Activity in
RESUMEDstate (user can see it and interact with it) - A
Servicerunning in the foreground (viastartForeground()) - A
BroadcastReceivercurrently executingonReceive()
Foreground processes are killed only as an absolute last resort.
2. Visible Process
A process whose Activity is visible but not focused:
- Activity is
PAUSED(e.g., covered by a dialog from another app, or split-screen adjacent) - A Service bound to a visible Activity
These processes are very important to the user experience and are kept alive as long as possible.
3. Service Process
A process running a Service started via startService() that is not foreground. Examples: music playback service (before calling startForeground), sync service.
These have been running for some time and their data is important, so Android tries to keep them alive. But under memory pressure they are killed before visible processes.
4. Cached (Background) Process
An Activity that is stopped (user navigated away). The process is in the LRU cache. Android kills these in LRU order — least recently used first.
5. Empty Process
A process with no active components. Kept only to improve cold-start time on next launch. First to be killed.
oom_adj Score
Each process is assigned an integer oom_adj score. Lower score = higher priority = less likely to be killed.
# View oom_adj for all processes
adb shell cat /proc/<pid>/oom_adj
# Or for all at once:
adb shell dumpsys activity processes | grep oom_adj
Typical ranges (can vary by device/kernel):
| Priority | oom_adj range |
|---|---|
| Foreground | -17 to 0 |
| Visible | 1 to 99 |
| Service | 100 to 199 |
| Cached/background | 200 to 906 |
| Empty | 906+ |
LMKD kills processes from highest oom_adj down until enough memory is freed. You cannot control oom_adj directly; it is computed by ActivityManagerService based on component state.
Application Class Callbacks
Your Application subclass receives memory pressure callbacks. Use them to drop caches and free resources.
class MyApplication : Application() {
override fun onLowMemory() {
// Deprecated in API 14 but still called for compatibility.
// Equivalent to TRIM_MEMORY_COMPLETE.
Glide.get(this).clearMemory()
clearInMemoryCaches()
}
override fun onTrimMemory(level: Int) {
when (level) {
// App is running, but system is low on memory.
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> {
// Start releasing non-critical caches (e.g., thumbnail cache)
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
// Release more. You are at risk of being killed.
Glide.get(this).onTrimMemory(level)
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// Release everything you can. You will soon be killed.
clearAllCaches()
}
// App was backgrounded.
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// Your UI has become hidden. Good time to release
// UI-related caches (bitmaps cached for animation, etc.)
releaseBitmapCache()
}
// App is in the background LRU list.
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> {
// Begining of LRU list. Release a small amount.
}
ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
// Middle of LRU list. Release more.
}
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
// Near the end of the LRU list. Release everything possible.
clearAllCaches()
}
}
}
}
onTrimMemory() is also called on Activity, Service, and Fragment — not just Application.
Activity Task & Back Stack
A Task is a stack of Activities the user has navigated through. The device Back button (or predictive back gesture) pops the top Activity.
Task A (foreground)
├── MainActivity ← bottom of stack
├── DetailActivity
└── EditActivity ← top (user is here)
Multiple tasks can exist simultaneously (e.g., user switches apps). The most recently used task's process gets a lower oom_adj score (higher priority).
Launch modes and their effect on the stack
// In AndroidManifest.xml
<activity
android:name=".DetailActivity"
android:launchMode="singleTop" />
// Or via Intent flags:
val intent = Intent(this, DetailActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
| Launch mode | Back stack behavior |
|---|---|
standard (default) | New instance always created |
singleTop | Reuses existing top instance; calls onNewIntent() |
singleTask | One instance per task; existing instance brought to top |
singleInstance | Isolated in its own task |
How Process Death Affects State
When Android kills your process (or the user force-stops it), all in-memory state is lost. The Activity lifecycle gives you hooks to save and restore UI state:
class DetailActivity : AppCompatActivity() {
private var scrollPosition: Int = 0
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Called before the Activity may be destroyed.
// NOT called when the user presses Back (intentional finish).
outState.putInt("SCROLL_POS", scrollPosition)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
// savedInstanceState is non-null only when recreating from saved state.
if (savedInstanceState != null) {
scrollPosition = savedInstanceState.getInt("SCROLL_POS", 0)
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
// Called after onCreate() if state was saved. Alternative restoration point.
scrollPosition = savedInstanceState.getInt("SCROLL_POS", 0)
}
}
ViewModel survives configuration changes, not process death
class DetailViewModel : ViewModel() {
// Survives rotation.
// Does NOT survive process death.
var data: List<Item> = emptyList()
}
// For process-death survival, use SavedStateHandle:
class DetailViewModel(private val savedState: SavedStateHandle) : ViewModel() {
var query: String
get() = savedState["query"] ?: ""
set(value) { savedState["query"] = value }
}
Verifying Process Death in Development
# Simulate the system killing your background process:
adb shell am kill com.example.app
# Force-stop (like Settings > Force Stop):
adb shell am force-stop com.example.app
# Enable "Don't keep activities" in Developer Options
# — destroys every Activity as soon as user leaves it,
# excellent for testing onSaveInstanceState coverage.
Practical Gotchas
- Never store state in
Applicationfields and assume it survives. On process re-launch after death,Application.onCreate()is called fresh. Check fornulleverywhere. onSaveInstanceStateis not called when the user presses Back. UseonPause()/onStop()to persist state to a database for intentional exits.- Foreground service does not make your process immune. Under extreme memory pressure, even foreground processes can be killed (rare). Persist critical state to disk.
- Worker processes (
:remotein manifest) have a separate PID and their own lifecycle. Crashes in worker processes do not crash the main process. onTrimMemory(TRIM_MEMORY_UI_HIDDEN)is the right place to drop bitmap caches — notonPause()which fires every time you show a dialog.- singleTask + deep links — if your app is already running and receives a deep link intent, the existing instance gets
onNewIntent(). Failing to handle this causes the deep link to be silently swallowed.
Summary
| Priority | Trigger | Killed when |
|---|---|---|
| Foreground | Resumed Activity or foreground Service | Almost never |
| Visible | Paused Activity or bound to visible | Rarely |
| Service | Background started Service | Moderate pressure |
| Cached | Stopped Activity in LRU | Regular background pressure |
| Empty | No components | First to go |
Design your app so that every screen can be recreated from scratch — either from a database, SavedStateHandle, or network — because the OS will eventually kill your process when you are in the background.