Android Profiler – Memory, GC, and Leaks That Slowly Kill Performance
Memory problems rarely crash your app.
They slow it down quietly, then surface as:
- jank after minutes of usage,
- ANRs that only happen on older devices,
- “works fine after restart” reports.
This article explains how memory behavior becomes a performance bug—long before a crash ever happens.
GC Is Not Cleanup. It Is Damage Control.
Garbage Collection exists to prevent failure, not to guarantee performance.
If GC runs frequently, it means:
- your app allocates too much,
- the runtime is constantly recovering,
- CPU time is stolen from frame rendering.
GC is the symptom. Allocation is the disease.
Memory Profiler Timeline: Trends Over Time Matter Most
One memory spike means nothing.
A memory trend tells the truth.
📸 Figure 5 – Memory Profiler Timeline
(Insert image here)
Android Memory Profiler timeline showing heap usage and garbage collection events to identify long-term memory growth.
Annotations (number these on the image):
- Heap size growth over time
- Garbage collection events
- Memory failing to return to baseline
Senior insight:
If memory never stabilizes after GC, you do not have a spike.
You have a leak—or sustained allocation pressure.
Allocation Tracking: Where Performance Quietly Dies
Allocations feel harmless.
Until they happen inside:
- scrolling,
- animations,
- repeated UI updates.
📸 Figure 6 – Allocation Tracking by Class
(Insert image here)
Memory allocation tracking grouped by class to reveal excessive object creation during UI interactions.
Annotations:
- Frequently allocated classes
- Allocations triggered by scroll or animation
- Short-lived objects creating GC pressure
Reality check:
Small objects allocated frequently are worse than large objects allocated rarely.
GC and UI Jank: The Hidden Relationship
GC pauses are usually short.
But they:
- interrupt Main thread work,
- shift frame timing,
- accumulate into visible jank.
A single GC pause might not hurt.
Repeated pauses always do.
Heap Dumps: When You Suspect a Leak, Prove It
Memory Profiler timelines suggest problems.
Heap dumps confirm them.
📸 Figure 7 – Heap Dump Dominator Tree
(Insert image here)
Heap dump dominator tree highlighting retained objects and reference chains responsible for memory leaks.
Annotations:
- Activity instances still retained after destruction
- Retained size dominating heap
- Reference chain through singleton or static field
Senior rule:
Shallow size explains object weight.
Retained size explains damage.
Classic Leak Patterns That Survive Code Reviews
Leaks often look reasonable in isolation.
Common culprits:
- Activities held by singletons,
- listeners never unregistered,
- callbacks capturing Context,
- caches with no eviction strategy.
The leak is rarely where memory grows.
It is where references never die.
Memory Pressure Is a CPU Problem in Disguise
As memory pressure increases:
- GC frequency increases,
- CPU time is stolen,
- frame deadlines are missed.
This is why memory bugs often show up as:
“The app feels slow after a while.”
It is not imagination.
It is physics.
Why “It Gets Fixed After Restart” Is a Red Flag
Restarting the app:
- clears the heap,
- resets allocation history,
- hides the real bug.
If performance only recovers after restart,
you have not fixed the problem.
You have reset it.
What This Part Intentionally Did Not Cover
- Thread scheduling
- System Trace
- Jetpack Compose recomposition
Those connect memory behavior to frame rendering.
They come next.
Final Thought
Junior developers ask:
“Why is memory high?”
Senior developers ask:
“Why does memory never come back down?”
Android Profiler already knows the answer.
You just need to read it over time.
Series Navigation
- Part 1 – CPU & Thread Reality
- Part 2 – Memory, GC, and Leaks ← you are here
- Part 3 – Compose, System Trace, and Frames
- Part 4 – Common Profiler Misreads
Top comments (0)