On this page
Garbage collection frees objects nothing references any more. A leak happens when something long-lived (a singleton, a static field, a running coroutine, a registered listener) keeps a reference to something short-lived, like an Activity that has already been destroyed. Every rotation can then add another dead Activity to memory.
How a leak happens
Common causes and fixes
| Cause | Example | Fix |
|---|---|---|
| Activity Context in a singleton | ImageLoader.init(activity) | Use applicationContext; don't store UI contexts |
| Unregistered listeners/callbacks | Location, sensor or broadcast listeners | Unregister in the matching lifecycle callback or DisposableEffect |
| Fragment View references | Binding kept after onDestroyView | Null out the binding in onDestroyView |
| Long-running work capturing UI | GlobalScope.launch { textView.text = โฆ } | Lifecycle-aware scopes; never capture Views in background work |
| Static or companion fields holding Views | companion object { var lastView: View? } | Don't. Pass IDs or state instead |
| Inner classes and anonymous handlers | A delayed Handler message referencing the Activity | Remove callbacks on destroy; prefer coroutines with lifecycle scopes |
| ViewModel holding a View or Activity | class VM(val activity: Activity) | ViewModels must not reference UI objects |
The Fragment binding leak
class ProfileFragment : Fragment(R.layout.fragment_profile) {
private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding!! // only valid between onViewCreated and onDestroyView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentProfileBinding.bind(view)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // the Fragment may outlive its View (back stack)
}
}Finding leaks with LeakCanary
dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:<version>") // debug builds only
}LeakCanary watches destroyed Activities, Fragments, Views and ViewModels. When one isn't collected, it dumps the heap, analyses it, and shows the leak trace: the reference chain from a GC root to the leaked object. Read it bottom-up to find the reference that shouldn't exist.
Investigating with Android Studio
- Open the Profiler and choose memory tracking for your app.
- Reproduce the suspected leak, for example by rotating several times or opening and closing a screen.
- Force garbage collection, then capture a heap dump.
- Filter by your Activity class. More instances than expected indicate a leak, and Android Studio can highlight leaked Activities and Fragments.
- Inspect the references of a leaked instance to find the GC root path.
Memory problems that aren't leaks
- Large bitmaps: decoding full-resolution images for thumbnails. Use an image library that downsamples.
- Unbounded caches: in-memory lists that grow forever. Use
LruCachewith limits. - Holding large data in UI state: page it with Paging, or keep it in the database.
Real investigations
OOM after many rotations
Leak trace: an analytics singleton held the Activity via a listener. Fix: application context and unregistering.
Fragment back stack growth
Bindings kept after onDestroyView. Fix: null out the binding, or move the screen to Compose.
Slow app after long use
Not a leak: an image cache without limits. Fix: size the cache and respond to onTrimMemory.
Prevention habits
- Use lifecycle-aware components and scopes (
viewModelScope,repeatOnLifecycle,DisposableEffect). - Inject the application context with Hilt's
@ApplicationContextinto long-lived objects. - Keep LeakCanary in debug builds and treat its reports as bugs.
- Compose reduces many View-related leaks, but listeners and contexts can still leak.
Frequently asked questions
Should I ship LeakCanary in release builds?
No. Add it with debugImplementation only. Heap dumps are expensive and may contain user data.
Does Kotlin or Compose eliminate leaks?
They reduce common causes, but any long-lived reference to a short-lived object still leaks.
Is a ViewModel holding Application context a leak?
No. The Application lives as long as the process. AndroidViewModel exists for this, though injecting specific dependencies is cleaner.
Conclusion
Memory leaks are reference paths from long-lived roots to dead objects, usually Activities, Fragments' Views or Contexts. Detect them with LeakCanary and heap dumps, fix the offending reference, and prevent them with lifecycle-aware scopes and application context.
Next step: tackle the other major stability issue in ANR Errors Explained.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Performance work starts with understanding how Android apps are built and how they behave at runtime. Strengthen those fundamentals and the profiling tools start to make sense.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.