Memory Leaks in Mobile Apps: Detection and Fix Guide (2026)

Memory leaks in mobile apps lead to slowdowns, OOM crashes, and bad reviews from users on budget devices. They are invisible in development where 8GB of RAM hides the problem, and painful in productio

June 03, 2026 · 4 min read · Common Issues

Memory leaks in mobile apps lead to slowdowns, OOM crashes, and bad reviews from users on budget devices. They are invisible in development where 8GB of RAM hides the problem, and painful in production where a user's 3GB phone hits the kill-limit after 20 minutes in your app. This guide covers root causes, detection, and fixes.

What a leak actually is

On Android, a leak is an object retained beyond its useful life — usually because a long-lived object holds a reference to a short-lived one (Activity, Fragment, View). On iOS, same pattern with strong reference cycles through delegates, closures, or NotificationCenter observers.

Leaks are distinct from high memory usage. A big image cache is not a leak if it gets released. A 50-byte object that is never freed, referenced 10,000 times through a static list, is a leak even though the numbers look small.

Common causes

Android

Static references to Activity/Context:


companion object {
    var lastActivity: Activity? = null  // LEAK
}

Fix: never hold Activity in a static. If you need application context, use application.applicationContext.

Long-lived listeners not unregistered:


override fun onStart() {
    super.onStart()
    eventBus.register(this)  // forgotten onStop
}

Fix: register/unregister in matching lifecycle pairs.

Handlers and runnables outliving their Activity:


handler.postDelayed(Runnable { updateUI() }, 60000)  // 60s delay

If the Activity dies in those 60 seconds, the Handler still holds it. Fix: use removeCallbacksAndMessages(null) in onDestroy or use a WeakReference.

AsyncTask or Coroutine outliving the scope:

Coroutines tied to lifecycleScope or viewModelScope handle this. Manual GlobalScope.launch is a leak if the launch holds references.

Bitmap references in lists not recycled:

Image loading libraries (Glide, Coil) handle this. Manual bitmap handling often does not.

iOS

Strong reference cycles:


class VC: UIViewController {
    var completion: (() -> Void)?
    override func viewDidLoad() {
        service.fetch { self.completion?() }  // LEAK — closure retains self
    }
}

Fix: [weak self] in closures, or unowned self if self's lifetime clearly outlives the closure.

Delegates not declared weak:


var delegate: MyDelegate?  // LEAK if the delegate also holds self
weak var delegate: MyDelegate?  // correct

NotificationCenter observers not removed:

In Swift 4+, block-based observers must be stored in NSObjectProtocol tokens and removed in deinit.

Timer blocks:


timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.tick() }
// Timer retains self via closure; timer is retained by RunLoop.

Fix: invalidate the timer in deinit or use [weak self].

Detection

Android

LeakCanary is the gold standard. Add to debug builds:


debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'

It hooks into lifecycle, detects retained objects, dumps heap, analyzes the reference chain, and notifies you with a stack trace pointing at the leak source. Low noise, high signal.

Android Studio Profiler — Memory tab. Record during a repro sequence. Look for:

Explicit heap dumps:


adb shell am dumpheap <pid> /data/local/tmp/heap.hprof
adb pull /data/local/tmp/heap.hprof
# Open in Android Studio or Eclipse MAT

iOS

Xcode Instruments — Leaks tool runs your app and flags detected cycles. Allocations tool shows heap growth over time.

Debug Memory Graph (Xcode menu: Debug → Capture Memory Graph) — visual graph of objects, filter by class, spot instances that should be deallocated but are not.

Automated detection:


class LeakDetector {
    private var instances = NSMapTable<AnyObject, NSString>.weakToStrongObjects()
    func track(_ obj: AnyObject, name: String) { instances.setObject(name as NSString, forKey: obj) }
    func detect() {
        for key in instances.keyEnumerator() { log("leaked: \(instances.object(forKey: key))") }
    }
}

Repro pattern

Leaks show up after repeated navigation. Navigate to the suspect screen, back out, repeat 20 times. Take a heap snapshot. If N copies of the screen's ViewModel / VC are retained, you have a leak. Use memory profiler to inspect retain path.

Fixes by pattern

  1. Retain cycles → weak/unowned
  2. Static references → avoid or use WeakReference / application context
  3. Unregistered listeners → register/unregister in matching lifecycle
  4. Timers → invalidate in deinit/onDestroy
  5. Bitmaps → rely on image-loading library; if manual, recycle explicitly
  6. Long-lived coroutines → attach to appropriate scope
  7. Views held by ViewModel → violate MVVM; refactor

Prevention

Static analysis catches some:

Code review checklist:

How SUSA catches leaks in exploration

SUSA's performance monitor samples memory every 5 seconds. A session that grows memory monotonically across a long exploration is flagged. Specifically:

The impatient persona reuses screens rapidly (navigate in, out, in, out) — exactly the pattern that surfaces navigation-related leaks. Memory growth over this kind of cycle is the best in-field leak detector.


susatest-agent test myapp.apk --persona impatient --steps 200

Example from a real app

JyotishyaMitra had an 80MB-over-50-steps leak: every navigation to the daily-horoscope screen added 1.6 MB retained. Root cause: the horoscope generator cached its output in a static Map keyed by date, but the values held references to the Activity's context via a formatter. Fix: move formatter out of the cached structure. Leak resolved, memory stable at 120MB for 200-step sessions.

Leaks are a solvable class. LeakCanary on every debug build, Instruments on every suspicious feature, SUSA on every release. Three layers catch 95% of what would otherwise ship.

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free