androidengineers.Book a session

Performance & Internals

Android Memory Management

article50 minHard

Android manages memory for you, but it cannot always clean up after poor code decisions. Understanding how the Android runtime handles memory helps you write apps that stay responsive, avoid crashes, and do not drain device resources.

How Android Allocates Memory

Each app runs in its own process with a private heap managed by the Android Runtime (ART). The heap starts small and grows as the app allocates objects. When the heap is full, the garbage collector (GC) runs.

GC pauses the app briefly. Frequent GC runs — often caused by allocating many short-lived objects in tight loops or during rendering — cause jank (dropped frames).

Reference Types

Java and Kotlin have four reference strengths. Choosing the right one prevents both leaks and unnecessary memory pressure.

ReferenceBehavior
Strong (default)Object is never GC'd while a strong reference exists
SoftReferenceGC'd when memory is low. Good for memory-sensitive caches
WeakReferenceGC'd at any time when no strong references exist
PhantomReferenceUsed for cleanup finalization; rarely needed directly
// Cache images without preventing GC
val cache = LinkedHashMap<String, WeakReference<Bitmap>>()

fun getCachedBitmap(key: String): Bitmap? {
    return cache[key]?.get() // returns null if GC'd
}

In practice, use LruCache (which uses strong references with a size limit) for most caching. Use WeakReference when you must hold an object but cannot guarantee it will not be GC'd.

Bitmap Memory

Bitmaps are the most common source of OutOfMemoryError on Android. A full-resolution 12MP photo decoded as ARGB_8888 takes ~48 MB in memory.

Best practices:

  • Always sample bitmaps down to the display size. Do not decode the full resolution if you only show a 200×200 thumbnail.
  • Use image loading libraries (Coil, Glide, Picasso). They handle sampling, caching, and lifecycle correctly.
  • Prefer inSampleSize when decoding manually.
  • Recycle bitmaps if you manage them manually (less common since API 26+ handles this in native memory).

Object Allocation in Rendering

Creating objects inside onDraw, @Composable functions that recompose frequently, or tight loops increases GC pressure.

// Bad: allocates a new Paint on every draw call
override fun onDraw(canvas: Canvas) {
    val paint = Paint().apply { color = Color.RED }
    canvas.drawCircle(cx, cy, radius, paint)
}

// Good: allocate once
private val paint = Paint().apply { color = Color.RED }

override fun onDraw(canvas: Canvas) {
    canvas.drawCircle(cx, cy, radius, paint)
}

The same applies in Compose: avoid creating new lambda objects or collections inside frequently recomposed functions.

Memory Budgets

Android enforces per-process memory limits that vary by device. You can query the limit:

val activityManager = getSystemService(ActivityManager::class.java)
val memoryClass = activityManager.memoryClass       // MB in normal apps
val largeMemoryClass = activityManager.largeHeapClass // MB with largeHeap enabled

Request android:largeHeap="true" in the manifest only if genuinely needed (photo editors, video players). It delays OOM but does not eliminate it, and it reduces how many apps can run simultaneously on the device.

Android Profiler: Memory Tab

The Memory Profiler in Android Studio shows:

  • Current heap size and usage
  • Object allocation over time
  • GC events
  • Heap dumps for detailed inspection

Workflow:

  1. Run the app, navigate to a screen you suspect has a leak.
  2. Click Record in the Memory Profiler.
  3. Interact with the screen, navigate away, and come back.
  4. Force GC and take a heap dump.
  5. Look for unexpected instances of Activity, Fragment, ViewModel, or large Bitmap objects.

OnTrimMemory

Android notifies your app when memory is low via onTrimMemory. Clear non-essential caches to avoid being killed.

override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
        imageCache.evictAll()
    }
}

Practice

Open the Memory Profiler on an app you own. Take a heap dump before and after navigating to a heavy screen and back. Compare the Retained Size of Bitmap objects between the two dumps. If bitmaps are still retained after leaving the screen, investigate why the image library is not releasing them.

Summary

Android GC runs automatically but cannot prevent leaks or OOM from poor choices. Use appropriate reference types, avoid per-frame allocations, decode bitmaps at display size, use image libraries, respond to onTrimMemory, and use the Memory Profiler to measure before optimizing.

YOUR LEARNING JOURNEY

0 of 17 available lessons completed

Progress saved in this browser. No account needed.
Android Memory Management | Senior Android Developer | Android Engineers