Process & Memory Management

📖 Concept

Android's memory management directly impacts app stability, performance, and whether the OS kills your process. Senior engineers must understand how ART's garbage collector works and how to prevent memory issues.

Android's memory model:

  • Each app runs in its own Linux process with its own instance of ART
  • Each process has a heap limit (typically 256-512MB depending on device)
  • The system has a Low Memory Killer (LMK) daemon that kills processes when RAM is low
  • Process priority determines kill order (foreground → visible → service → cached)

ART Garbage Collection:

  • Uses Concurrent Copying GC (Android 8.0+) — moves objects while app runs
  • GC roots: stack variables, static fields, JNI references, active threads
  • Pause times typically < 1ms (huge improvement over Dalvik's stop-the-world GC)

Memory types in Android:

  • Java Heap: Objects created in Kotlin/Java. Counted toward heap limit.
  • Native Heap: Memory allocated via NDK (malloc). Not counted toward Java heap limit but limited by system RAM.
  • Graphics Memory: Bitmaps, textures (GPU). Major contributor to OOM since large images can consume tens of MB.
  • Stack: Thread stacks (typically 1MB each). More threads = more stack memory.

Common memory issues:

  1. Memory Leak: Objects held beyond their useful life → steadily increasing heap
  2. OOM (OutOfMemoryError): Heap limit exceeded, usually from large bitmaps or leaked Activities
  3. GC thrashing: Rapid allocation and deallocation causing frequent GC pauses

Profiling tools:

  • Android Studio Memory Profiler — live heap inspection
  • LeakCanary — automatic leak detection in debug builds
  • adb shell dumpsys meminfo <package> — process memory breakdown

💻 Code Example

codeTap to expand ⛶
1// Common memory leak patterns and fixes
2
3// LEAK 1: Static reference to Activity ❌
4object DataCache {
5 var callback: ((String) -> Unit)? = null // Holds Activity reference!
6}
7class LeakyActivity : AppCompatActivity() {
8 override fun onCreate(savedInstanceState: Bundle?) {
9 super.onCreate(savedInstanceState)
10 DataCache.callback = { data -> textView.text = data } // Leak!
11 }
12 // Fix: clear in onDestroy OR use WeakReference
13}
14
15// LEAK 2: Inner class holding outer reference ❌
16class LeakyActivity2 : AppCompatActivity() {
17 inner class MyTask : Runnable { // inner class holds ref to Activity
18 override fun run() { /* long-running task */ }
19 }
20 // Fix: Use static nested class or top-level class
21}
22
23// LEAK 3: Handler message queue ❌
24class LeakyActivity3 : AppCompatActivity() {
25 private val handler = Handler(Looper.getMainLooper())
26 override fun onCreate(savedInstanceState: Bundle?) {
27 super.onCreate(savedInstanceState)
28 handler.postDelayed({ updateUI() }, 60_000) // Holds ref for 60s
29 }
30 // Fix: Remove callbacks in onDestroy
31 override fun onDestroy() {
32 handler.removeCallbacksAndMessages(null)
33 super.onDestroy()
34 }
35}
36
37// Proper bitmap handling to prevent OOM
38suspend fun loadBitmap(context: Context, uri: Uri, reqWidth: Int, reqHeight: Int): Bitmap {
39 return withContext(Dispatchers.IO) {
40 // Step 1: Decode bounds only (no memory allocation)
41 val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
42 context.contentResolver.openInputStream(uri)?.use {
43 BitmapFactory.decodeStream(it, null, options)
44 }
45 // Step 2: Calculate sample size for downscaling
46 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
47 options.inJustDecodeBounds = false
48 // Step 3: Decode with downscaled dimensions
49 context.contentResolver.openInputStream(uri)?.use {
50 BitmapFactory.decodeStream(it, null, options)
51 } ?: throw IOException("Cannot open stream")
52 }
53}
54
55// LeakCanary setup (debug builds only)
56// Add to build.gradle: debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.x'
57// LeakCanary auto-initializes and watches Activities, Fragments, Views, ViewModels

🏋️ Practice Exercise

Practice:

  1. Set up LeakCanary and intentionally create a memory leak, then trace it
  2. Use Android Studio Memory Profiler to capture a heap dump and find retained objects
  3. Explain the difference between shallow size and retained size in a heap dump
  4. Write a bitmap loading function that prevents OOM by downsampling
  5. Explain how Glide/Coil handle bitmap memory efficiently
  6. Run adb shell dumpsys meminfo for your app and explain each memory category

⚠️ Common Mistakes

  • Registering callbacks without unregistering — the most common cause of Activity leaks

  • Loading full-resolution bitmaps into memory — a 12MP camera photo = ~48MB in memory, always downsample

  • Creating anonymous inner classes that capture Activity references — use static nested classes or lambdas with weak references

  • Not using LeakCanary in debug builds — memory leaks are silent until they crash

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Process & Memory Management. Login to unlock this feature.