The Android Memory Profiler and LeakCanary are the two tools you'll spend the most time with when hunting memory issues. They complement each other: the Profiler shows the overall heap picture; LeakCanary pinpoints the exact reference chain causing a leak.
Android Studio Memory Profiler
How to access: Run → Profile app → Memory tab → Start profiler.
Key Views
Heap dump — snapshot of all live objects on the heap. Captures:
- Object count per class
- Shallow size (the object's own memory)
- Retained size (total memory freed if this object were collected)
Run the app → perform the suspicious action several times
→ click "Dump Java Heap" → wait for capture
Allocation tracking — records every object allocation over a time window:
Click "Record" → perform the action → "Stop"
→ sort by "Count" or "Size"
→ look for objects that grow unboundedly
Reading a Heap Dump
- Sort by Retained Size descending — biggest memory holders at the top
- Look for
ActivityorFragmentinstances with count > 1 (leak indicator) - Click an instance → References panel shows what's holding it
- Follow the chain from GC Root → leaked object
GC Root → MainThread.localVariables
→ Handler → Activity (should be destroyed)
→ View (inside the Activity)
LeakCanary
LeakCanary automates heap dump analysis. It detects when an Activity/Fragment should be collected but isn't, takes a heap dump, analyzes it, and shows a human-readable leak trace.
Setup
// build.gradle.kts — add to debug only
dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
}
// No Application.onCreate code needed — LeakCanary auto-installs itself
Reading a Leak Trace
┬───
│ GC Root: Thread (main thread)
│
├─ android.os.Handler
│ leaking: NO (Handler is reachable)
│ handler type: MyActivity$Companion$myHandler$1
│
├─ MyActivity
│ leaking: YES (Activity destroyed)
│ reason: MyActivity#mDestroyed is true
│
╰→ android.widget.TextView
leaking: YES (View attached to a destroyed Activity)
Read from bottom to top:
- Bottom: the leaking object (Activity/View)
- Top: what's holding the reference
- The fix is at the top: stop the top-most object from holding the reference
Custom Watch Targets
By default, LeakCanary watches Activities and Fragments. Add custom objects:
// Watch when a ViewModel should be cleared
class MyViewModel : ViewModel() {
override fun onCleared() {
AppWatcher.objectWatcher.expectWeaklyReachable(this, "ViewModel was cleared")
}
}
// Watch a custom object
AppWatcher.objectWatcher.expectWeaklyReachable(
someObject,
"Description of where this object was created"
)
Memory Profiler Workflow for Suspected Leaks
1. Launch app → go to the screen with the suspected leak
2. Press Back or finish the Activity
3. Force GC: click "Force GC" button in profiler
4. Dump heap
5. Search for the Activity class in the heap dump
6. If Count > 0 → it's leaked
7. Click the instance → References → trace the chain
Common LeakCanary Findings and Fixes
| Leak trace signature | Fix |
|---|---|
InputMethodManager → Activity | Platform bug; no fix needed (LeakCanary ignores known platform leaks) |
Handler → Activity | Remove callbacks in onDestroy/onPause |
| Static field → Activity | Use applicationContext; don't store Activity in statics |
Cursor not closed | Close cursors in finally blocks |
RecyclerView.Adapter → Fragment | Set adapter to null in onDestroyView |
Key Takeaways
| Tool | Best for |
|---|---|
| Memory Profiler (heap dump) | Understanding what's using heap; finding leaked instances |
| Memory Profiler (allocation) | Finding which code path allocates too many objects |
| LeakCanary | Automated leak detection with exact reference chain |
AppWatcher.objectWatcher | Verifying custom objects are collected when expected |