A memory leak detection library for Android.
โA small leak will sink a great ship.โ - Benjamin Franklin
Add LeakCanary to build.gradle
:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}
That's it, there is no code change needed! LeakCanary will automatically show a notification when a memory leak is detected in debug builds.
What's next?
- Learn the Fundamentals
- Try the code recipes
- Read the FAQ
- Watch recorded presentations
- Read articles
Note: LeakCanary 2 is in alpha.
- Check out the migration guide.
- Here is the change log.
- To set up LeakCanary 1.6, go to the 1.6 Readme.
In a Java based runtime, a memory leak is a programming error that causes an application to keep a reference to an object that is no longer needed. As a result, the memory allocated for that object cannot be reclaimed, eventually leading to an OutOfMemoryError crash.
For example, an Android activity instance is no longer needed after its onDestroy()
method is called, and storing a reference to that activity in a static field would prevent it from being garbage collected.
Memory leaks are very common in Android apps. OutOfMemoryError (OOM) is the top crash for most apps on the play store, however that's usually not counted correctly. When memory is low the OOM can be thrown from anywhere in your code, which means every OOM has a different stacktrace and they're counted as different crashes.
When we first enabled LeakCanary in the Square Point Of Sale app, we were able to find and fix several leaks and reduced the OutOfMemoryError crash rate by 94%.
- The library automatically watches destroyed activities and destroyed fragments using weak references. You can also watch any instance that is no longer needed, e.g. a detached view.
- If the weak references aren't cleared, after waiting 5 seconds and running the GC, the watched instances are considered retained, and potentially leaking.
- When the number of retained instances reaches a threshold, LeakCanary dumps the Java heap into a
.hprof
file stored on the file system. The default threshold is 5 retained instances when the app is visible, 1 otherwise. - LeakCanary parses the
.hprof
file and finds the chain of references that prevents retained instances from being garbage collected (leak trace). A leak trace is technically the shortest strong reference path from GC Roots to retained instances, but that's a mouthful. - Once the leak trace is determined, LeakCanary uses its built in knowledge of the Android framework to deduct which instances in the leak trace are leaking. You can help LeakCanary by providing Reachability inspectors tailored to your own app.
- Using the reachability information, LeakCanary narrows down the reference chain to a sub chain of possible leak causes, and displays the result. Leaks are grouped by identical sub chain.
To fix a memory leak, you need to look at the sub chain of possible leak causes and find which reference is causing the leak, i.e. which reference should have been cleared at the time of the leak. LeakCanary highlights with a red underline wave the references that are the possible causes of the leak.
If you cannot figure out a leak, please do not file an issue. Instead, create a Stack Overflow question using the leakcanary tag.
If you think a recipe might be missing or you're not sure that what you're trying to achieve is possible with the current APIs, please file an issue. Your feedback help us make LeakCanary better for the entire community.
LeakCanary is released as two distinct libraries:
- LeakSentry
- Detects retained instances.
- Suitable for release builds.
- Artifact id:
com.squareup.leakcanary:leaksentry
.
- LeakCanary
- Dumps the heap and analyzes it.
- Currently only suitable for debug builds.
- Depends on LeakSentry.
- Artifact id:
com.squareup.leakcanary:leakcanary-android
.
LeakSentry can be configured by replacing LeakSentry.config
:
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
LeakSentry.config = LeakSentry.config.copy(watchFragmentViews = false)
}
}
LeakCanary can be configured by replacing LeakCanary.config
:
disableLeakCanaryButton.setOnClickListener {
LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)
}
In your application, you may have other objects with a lifecycle, such as fragments, services, Dagger components, etc. Use LeakSentry.refWatcher
to watch instances that should be garbage collected:
class MyService : Service {
// ...
override fun onDestroy() {
super.onDestroy()
LeakSentry.refWatcher.watch(this)
}
}
com.squareup.leakcanary:leakcanary-android
should only be used in debug builds. It depends on com.squareup.leakcanary:leaksentry
which you can use in production to track and count retained instances.
In your build.gradle
:
dependencies {
implementation 'com.squareup.leakcanary:leaksentry:2.0-alpha-2'
}
In your leak reporting code:
val retainedInstanceCount = LeakSentry.refWatcher.retainedKeys.size
Add the leakcanary-android-instrumentation
dependency to your instrumentation tests:
androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:${leakCanaryVersion}"
Add the dedicated run listener to defaultConfig
in your build.gradle
:
android {
defaultConfig {
// ...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument "listener", "com.squareup.leakcanary.FailTestOnLeakRunListener"
}
}
Run the instrumentation tests:
./gradlew leakcanary-sample:connectedCheck
You can extend FailTestOnLeakRunListener
to customize the behavior.
The activity that displays leaks comes with a default icon and label, which you can change by providing R.mipmap.leak_canary_icon
and R.string.leak_canary_display_activity_label
in your app:
res/
mipmap-hdpi/
leak_canary_icon.png
mipmap-mdpi/
leak_canary_icon.png
mipmap-xhdpi/
leak_canary_icon.png
mipmap-xxhdpi/
leak_canary_icon.png
mipmap-xxxhdpi/
leak_canary_icon.png
mipmap-anydpi-v26/
leak_canary_icon.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="leak_canary_display_activity_label">MyLeaks</string>
</resources>
You can change the default behavior to upload the analysis result to a server of your choosing.
Create a custom AnalysisResultListener
that delegates to the default:
class LeakUploader : AnalysisResultListener {
override fun invoke(
application: Application,
heapAnalysis: HeapAnalysis
) {
TODO("Upload heap analysis to server")
// Delegate to default behavior (notification and saving result)
DefaultAnalysisResultListener(application, heapAnalysis)
}
}
Set analysisResultListener
on the LeakCanary config:
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
LeakCanary.config = LeakCanary.config.copy(analysisResultListener = LeakUploader())
}
}
Set exclusionsFactory
on the LeakCanary config to a ExclusionsFactory
that delegates to the default one and then and add custom exclusions:
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
LeakCanary.config = LeakCanary.config.copy(exclusionsFactory = { hprofParser ->
val defaultFactory = AndroidExcludedRefs.exclusionsFactory(AndroidExcludedRefs.appDefaults)
val appDefaults = defaultFactory(hprofParser)
val customExclusion = Exclusion(
type = StaticFieldExclusion("com.thirdparty.SomeSingleton", "sContext"),
status = Exclusion.Status.WONT_FIX_LEAK,
reason = "SomeSingleton in library X has a static field leaking a context."
)
appDefaults + customExclusion
})
}
}
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
val customLabeler: Labeler = { parser, node ->
listOf("Heap dump object id is ${node.instance}")
}
val labelers = AndroidLabelers.defaultAndroidLabelers(this) + customLabeler
val customInspector: LeakInspector = { parser, node ->
with(parser) {
if (node.instance.objectRecord.isInstanceOf("com.example.MySingleton")) {
LeakNodeStatus.notLeaking("MySingleton is a singleton")
} else LeakNodeStatus.unknown()
}
}
val leakInspectors = AndroidLeakInspectors.defaultAndroidInspectors() + customInspector
LeakCanary.config = LeakCanary.config.copy(labelers = labelers, leakInspectors = leakInspectors)
}
}
Yes. There are a number of known memory leaks that have been fixed over time in AOSP as well as in manufacturer implementations. When such a leak occurs, there is little you can do as an app developer to fix it. For that reason, LeakCanary has a built-in list of known Android leaks to ignore: AndroidExcludedRefs.kt.
If you find a new one, please create an issue and follow these steps:
- Provide the entire leak trace information (reference key, device, etc), and use backticks (`) for formatting.
- Read the AOSP source for that version of Android, and try to figure out why it happens. You can easily navigate through SDK versions android/platform_frameworks_base.
- Check if it happens on the latest version of Android, and otherwise use blame to find when it was fixed.
- If it's still happening, build a simple repro case
- File an issue on b.android.com with the leak trace and the repro case
- Create a PR in LeakCanary to update
AndroidExcludedRefs.kt
. Optional: if you find a hack to clear that leak on previous versions of Android, feel free to document it.
- Go to the leak screen, click the overflow menu and select Share Info.
- You can also find the leak trace in Logcat.
Sometimes the leak trace isn't enough and you need to dig into a heap dump with MAT or YourKit.
- Go to a heap analysis screen, click the overflow menu and select Share Heap Dump.
Here's how you can find the leaking instance in the heap dump:
- Look for all instances of
leakcanary.KeyedWeakReference
- For each of these, look at the
key
field. - Find the
KeyedWeakReference
that has akey
field equal to the reference key reported by LeakCanary. - The
referent
field of thatKeyedWeakReference
is your leaking object. - From then on, the matter is in your hands. A good start is to look at the shortest path to GC Roots (excluding weak references).
0. LeakCanary is a debug only library.
Update your dependencies to the latest SNAPSHOT (see build.gradle):
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-3-SNAPSHOT'
}
Add Sonatype's snapshots
repository:
repositories {
mavenCentral()
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
LeakCanary was created and open sourced by @pyricau, with many contributions from the community.
The name LeakCanary is a reference to the expression canary in a coal mine, because LeakCanary is a sentinel used to detect risks by providing advance warning of a danger. Props to @edenman for suggesting it!
- @pyricau quickly made the first version of the logo. It was based on cliparts from Android Asset Studio, mixed with the selection from a photo of a Canary. The exclamation mark means danger, the shield stands for protection, and the bird, well, is a canary.
- @romainguy turned the ugly logo into a nice vector asset.
- @flickator designed a much nicer logo for LeakCanary 2.0!
- LeakCanary 2: Leaner, Better, Faster, Kotliner!
- LeakCanary, then what? Nuking Nasty Memory Leaks
- Memory Leak Hunt, a live investigation.
Your presentation should be here, please update this list! Any technical level welcome.
Your article should be here, please update this list! Any technical level welcome.
Copyright 2015 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.