LeakCanary is an open-source library by Square for automatic memory leak detection in Android applications. It integrates into the development process and monitors the lifecycle of Activity, Fragment, ViewModel, and other components in real time, signaling leaks as soon as they occur. According to Square Open Source, the library is used in thousands of projects and is considered the de facto standard for memory diagnostics on Android.
Key Takeaways
LeakCanary is a library for automatic memory leak detection in Android applications, developed by Square. It integrates into the app build process and automatically monitors whether objects that should be destroyed (Activity, Fragment, View) remain in memory. When a leak is detected, LeakCanary generates a heap dump and analyzes the reference chain holding the object.
The library has become a standard in the Android community: according to GitHub, the project has over 28 thousand stars and is used in apps by Google, Uber, Airbnb, and Facebook. LeakCanary is available in two main versions: classic 1.x (with manual configuration) and modern 2.x (automatic integration via ContentProvider). Version 2.x does not require modifying the Application class — the dependency alone is enough for full functionality.
LeakCanary’s main task is to detect when an object continues to exist in memory after its lifecycle has ended. This is typical for leaks through static fields, singletons, unregistered callbacks, anonymous classes, and closures that capture external objects.
Memory leaks on Android are more critical than on desktop due to the limited RAM on mobile devices. Even a 5–10 MB leak on each screen transition can lead to an OutOfMemoryError after 30–40 minutes of app usage. LeakCanary detects such issues at the development stage, without waiting for a crash in production.
LeakCanary uses weak references (WeakReference) combined with forced garbage collection. When an Activity or Fragment calls onDestroy, LeakCanary creates a WeakReference to that object and triggers GC after a short delay (5 seconds by default). If the object is still accessible via WeakReference after GC, it is being held by a strong reference — a leak is recorded.
After detecting a leak, LeakCanary makes a heap dump (memory dump) — a complete snapshot of the app’s memory in HPROF format. The built-in analyzer (Shark for version 2.x) then builds a reachability graph from GC Roots to the leaked object and finds the shortest path — the reference chain keeping the object in memory.
// Simplified LeakCanary detection logic
class ObjectWatcher {
private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()
fun watch(watchedObject: Any, description: String) {
val reference = KeyedWeakReference(watchedObject, description)
watchedReferences.add(reference)
BackgroundHandler.postDelayed({
checkForLeaks()
}, 5000)
}
private fun checkForLeaks() {
GcTrigger.runGc() // forced GC
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // object survived GC — it’s a leak
}
}
}
}
The key point is the forced call to GcTrigger.runGc(). Without it, it is impossible to distinguish an object that has actually leaked from one that the GC has not yet collected. LeakCanary does this up to three times: if after three GC cycles the object is still in memory, the leak is confirmed.
Shark is the built-in heap dump analyzer in LeakCanary 2.x, written in Kotlin. Unlike the previous HAHA analyzer, Shark does not load the entire HPROF file into memory but traverses its object graph with minimal allocations. This reduces RAM consumption during analysis from 50 MB to 2–5 MB and shortens analysis time from 30 seconds to 1–3 seconds.
Installing LeakCanary 2.x in a modern Android project takes one line in build.gradle. The library uses ContentProvider for automatic initialization — no need to modify the Application class or add any code to MainActivity. The dependency is added only for debug builds so that release APKs do not contain extra code.
// build.gradle (app/module)
dependencies {
// debugImplementation — library only for debug builds
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
After adding the dependency and rebuilding the project, LeakCanary automatically appears in the app. On the first launch, the library shows a system notification confirming activation. All detected leaks appear as notifications — tapping a notification opens a screen with a detailed report (LeakTrace).
For customization, you can create your own AppWatcherInstaller and override parameters: GC timeout, list of tracked object types, enabling heap dump saving to disk. However, for 90% of projects, the default configuration is optimal.
Starting with version 2.12, LeakCanary supports automatic tracking of ViewModel, coroutine scopes, and Compose State objects. No additional dependencies are needed — the library automatically detects which Jetpack components are used in the project and activates the corresponding detectors.
A LeakCanary report (LeakTrace) is a multi-line reference chain from GC Root to the leaked object. Each line shows the class and field through which a strong reference passes. Developers should read the chain from bottom to top: the bottom line is the leaked object, the top line is the entry point (GC Root).
A typical LeakTrace looks like this: GC Root → static Application field → singleton → callback → Activity. If a developer sees such a chain, the problem is clear: the singleton holds a callback that captured a reference to the Activity. The solution is to replace the strong reference with a weak one in the singleton.
┬
├─ android.app.Application
│ Leaking: NO (Application — singleton)
│ ↓ Application.leakedActivities
├─ java.util.ArrayList
│ Leaking: NO (ArrayList — normal)
│ ↓ ArrayList[0]
├─ com.example.MainActivity
│ Leaking: YES (Activity destroyed but still in memory)
│ ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│ Leaking: UNKNOWN
│ ↓ CallbackWrapper.mListener
│ ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│ Leaking: UNKNOWN
│ ↓ MyCallback.this$0
├─ com.example.MainActivity
│ Leaking: YES (MainActivity is the leak)
╰
In this example, LeakCanary shows that MainActivity is retained through the chain: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → MainActivity again. The this$0 arrow indicates that the anonymous class MyCallback captured an external reference to the Activity. The solution is to make the callback a weak reference or cancel it in onDestroy.
LeakCanary also shows the leak status for each element in the chain: NO (no leak — this is a root element), YES (object should be destroyed), UNKNOWN (could not determine status). UNKNOWN status does not mean there is a problem — it is an intermediate object that LeakCanary cannot definitively classify.
The transition from version 1.x to 2.x was radical: the developers rewrote the library from scratch, replacing the outdated HAHA analyzer with their own engine, Shark, written in Kotlin. Shark is an order of magnitude faster, requires less memory for analysis, and more accurately determines the root causes of leaks.
| Parameter | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Analyzer language | Java (HAHA — fork of Android SDK) | Kotlin (Shark — custom engine) |
| Setup | Manual AppWatcher configuration in Application | Automatic via ContentProvider |
| Speed | 10–30 seconds for heap dump analysis | 1–5 seconds for heap dump analysis |
| Performance | Takes 10–50 MB RAM during analysis | Takes 2–10 MB RAM during analysis |
The key advantage of Shark is that it does not load the entire heap dump into memory, but traverses its reference graph with minimal allocations. This makes LeakCanary 2.x suitable for use on devices with low RAM without the risk of OutOfMemoryError during analysis.
Version 2.x also introduced the ability to export heap dumps to a file for later analysis in Android Studio Memory Profiler. To do this, enable the dumpHeapWhenLeakFound setting in the AppWatcher configuration.
LeakCanary effectively detects several classes of leaks common to Android. The most frequent is leaking through static references to an Activity — developers keep a reference to the Activity context in a singleton, and the Activity cannot be collected by GC after its lifecycle ends.
The second most common category is leaks through unregistered listeners. If registerListener was called in onStart but unregisterListener was not called in onStop/onDestroy, the listener object is held by the system even after the activity is destroyed. LeakCanary clearly shows which listener and in which system service remains alive.
// Typical leak: Activity captured in a singleton callback
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // strong reference to callback
}
fun unregister() {
callback = null // DON’T FORGET to call in onDestroy!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda captures this
}
// if unregister is not called in onDestroy → Activity leak
}
}
The third category is leaks through Fragment in BackStack. If FragmentTransaction.addToBackStack() is called without removing the Fragment on back navigation, old Fragment instances remain in memory. LeakCanary helps detect such hidden leaks early in development.
For each detected leak, LeakCanary provides a description and recommendations for fixing it. Version 2.14 added integration with Android Lint — the library can automatically create issue tracker tasks when a leak is detected in CI.
Frequently Asked Questions
Yes, absolutely. LeakCanary is added via debugImplementation in build.gradle, which automatically excludes it from release builds. If you use implementation instead, the library will be included in the release APK and will show leaks to end users — this is unacceptable.
The performance impact is minimal. LeakCanary only activates after a component’s onDestroy and does not interfere with UI rendering or touch handling. The only cost is a short forced GC pause (about 100 ms) and writing a heap dump when a leak occurs (a fraction of a second).
LeakCanary automatically saves heap dumps in HPROF format to the app’s folder. The file can be exported via Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. To view it, open the file in Memory Profiler via Capture → Open Heap Dump.
Yes, starting with version 2.12 LeakCanary fully supports Jetpack Compose. The library tracks Composition contexts and State objects, automatically detecting leaks in Composable functions. No separate configuration is needed — it works out of the box.
False positives are possible but rare. LeakCanary uses a triple GC call before declaring a leak, which eliminates most false positives. If you believe a detection is a false positive, create an IgnoredReference for the specific class in the configuration.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also