Skip to content

Latest commit

 

History

History
299 lines (203 loc) · 12.9 KB

README.md

File metadata and controls

299 lines (203 loc) · 12.9 KB

LeakCanary

A memory leak detection library for Android.

“A small leak will sink a great ship.” - Benjamin Franklin

Getting started

Add LeakCanary to build.gradle:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-1'
}

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?

Note: LeakCanary 2 is in alpha.

Fundamentals

What is a memory leak?

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.

Why should I use LeakCanary?

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%.

How does LeakCanary work?

  • 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.

How do I fix a memory leak?

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.

Code Recipes

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.

Configuring LeakSentry & LeakCanary

LeakCanary is released as two distinct libraries: com.squareup.leakcanary:leaksentry and com.squareup.leakcanary:leakcanary-android which depends on leaksentry.

LeakSentry is in charge of detecting retained instances. Its configuration can be updated at any time by replacing LeakSentry.config:

class DebugExampleApplication : ExampleApplication() {

  override fun onCreate() {
    super.onCreate()
    LeakSentry.config = LeakSentry.config.copy(watchFragmentViews = false)
  }
}

LeakCanary is in charge of dumping the heap and analyzing it. Its configuration can be updated at any time by replacing LeakCanary.config:

disableLeakCanaryButton.setOnClickListener {
  LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)
}

Watching objects with a lifecycle

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)
  }
}

Counting retained instances in production

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-1'
}

In your leak reporting code:

val retainedInstanceCount = LeakSentry.refWatcher.retainedKeys.size

Running LeakCanary in instrumentation tests

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.

Icon and label

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>

Uploading to a server

You can change the default behavior to upload the leak trace and heap dump to a server of your choosing.

TODO Document this

Identifying AOSP leaks as "won't fix"

TODO Document this

Identifying leaking instances and labeling instances

TODO Document this

FAQ

Can a leak be caused by the Android SDK?

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:

  1. Provide the entire leak trace information (reference key, device, etc), and use backticks (`) for formatting.
  2. 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.
  3. Check if it happens on the latest version of Android, and otherwise use blame to find when it was fixed.
  4. If it's still happening, build a simple repro case
  5. File an issue on b.android.com with the leak trace and the repro case
  6. 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.

How do I share a leak trace?

  • Go to the leak screen, click the overflow menu and select Share Info.
  • You can also find the leak trace in Logcat.

How can I dig beyond the leak trace?

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:

  1. Look for all instances of leakcanary.KeyedWeakReference
  2. For each of these, look at the key field.
  3. Find the KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary.
  4. The referent field of that KeyedWeakReference is your leaking object.
  5. 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).

How many methods does LeakCanary add?

0. LeakCanary is a debug only library.

How do I use the SNAPSHOT version?

Update your dependencies to the latest SNAPSHOT (see build.gradle):

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2-SNAPSHOT'
 }

Add Sonatype's snapshots repository:

  repositories {
    mavenCentral()
    maven {
      url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
  }

Status of the snapshot build: Build Status

Who's behind LeakCanary?

LeakCanary was created and open sourced by @pyricau, with many contributions from the community.

Why is it called LeakCanary?

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!

Who made the logo?

Presentations

Your presentation should be here, please update this list! Any technical level welcome.

Articles

Your article should be here, please update this list! Any technical level welcome.

License

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.