What is LeakCanary

LeakCanary is square’s open source tool for detecting Memory leaks on Android. It is easy to use and cheap to access. .

Method of use

/ / module build gradle add depend on implementation 'com. Squareup. Leakcanary: leakcanary - android: 1.6.3' public class App extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); }Copy the code

Then run the program and if LeakCanary detects a possible memory leak, it will send a notification and click to see the chain of references to the memory leak. Of course, you will also have to review the code yourself to determine whether or not a memory leak has actually occurred, as LeakCanary is likely to misdiagnose. LeakCanary will only monitor Activity leaks for you by default. If you want to monitor an object for memory leaks, you need to manually watch the object yourself.

LeakCanary. Install (this) must be manually deleted, or add some judgment logic to the test package. If you manually watch other objects, Square has also provided a leakcanary-no-op library, which is empty implementation for compiling the official package. The final dependencies can be written as follows:

/ / module build gradle add dependent testImplementation 'com. Squareup. Leakcanary: leakcanary - android: 1.6.3' debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 1.6.3' releaseImplementation 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.6.3'Copy the code

Leakcanary – Android-no-op RefWatcher code:

/** * No-op implementation of {@link RefWatcher} for release builds. Please use {@link * RefWatcher#DISABLED}. */ public  final class RefWatcher { @NonNull public static final RefWatcher DISABLED = new RefWatcher(); private RefWatcher() { } public void watch(@NonNull Object watchedReference) { } public void watch(@NonNull Object watchedReference, @NonNull String referenceName) { } }Copy the code

The principle of analysis

  1. LeakCanary install method:

     public static @NonNull RefWatcher install(@NonNull Application application) {
     	return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        		.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
         	.buildAndInstall();
     }
    Copy the code
  2. The refWatcher(Application) method returns an AndroidRefWatcherBuilder object, the listenerServiceClass method specifies the service class for displaying memory leak notifications, the excludedRefs method specifies the whitelist, This whitelist can be configured by itself to avoid LeakCanary misdetection. Next look at the buildAndInstall method:

    public @NonNull RefWatcher buildAndInstall() { if (LeakCanaryInternals.installedRefWatcher ! = null) { throw new UnsupportedOperationException("buildAndInstall() should only be called once."); } RefWatcher refWatcher = build(); if (refWatcher ! = DISABLED) { if (enableDisplayLeakActivity) { LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true); } if (watchActivities) { ActivityRefWatcher.install(context, refWatcher); } if (watchFragments) { FragmentRefWatcher.Helper.install(context, refWatcher); } } LeakCanaryInternals.installedRefWatcher = refWatcher; return refWatcher; }Copy the code
  3. Call ActivityRefWatcher. Install on the Activity of memory leak detection:

     public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
         Application application = (Application) context.getApplicationContext();
         ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
     
         application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
     }
    Copy the code
  4. RegisterActivityLifecycleCallbacks system apis, applications within any change an Activity lifecycle callback corresponding methods, including onDestroy, Look at ActivityRefWatcher’s lifecycleCallbacks callback:

    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { @Override public void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }};Copy the code
  5. The activity that is called back is the activity that called onDestroy and should theoretically be recycled next. With this object reference, RefWatcher’s Watch method is called:

     public void watch(Object watchedReference, String referenceName) {
         if (this == DISABLED) {
           return;
         }
         checkNotNull(watchedReference, "watchedReference");
         checkNotNull(referenceName, "referenceName");
         final long watchStartNanoTime = System.nanoTime();
         String key = UUID.randomUUID().toString();
         retainedKeys.add(key);
         final KeyedWeakReference reference =
             new KeyedWeakReference(watchedReference, key, referenceName, queue);
     
         ensureGoneAsync(watchStartNanoTime, reference);
     }
    Copy the code
  6. First generate a unique key, and then put it in the retainedKeys set, and then create a WeakReference reference to the Activity object, specify queue as its ReferenceQueue, Finally, call the ensureGoneAsync method to determine whether the Activity has a memory leak:

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); }}); }Copy the code
  7. The watchExecutor execute method internally adds an Idler message to the main thread, the ensureGone method, which runs on the main thread and only executes when the main thread is idle. Take a look at the ensureGone method:

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } if (gone(reference)) { return DONE; } gcTrigger.runGc(); removeWeaklyReachableReferences(); if (! gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); heapdumpListener.analyze(heapDump); } return DONE; }Copy the code
  8. This ensureGone is the key part, the first call removeWeaklyReachableReferences method, in this method, if the judge to queue queue is not empty, means that the garbage collector think the Activity object need to be recycled, then everything is normal, If no memory leaks have occurred, remove the Activity’s unique key from the retainedKeys, then the gone method will determine that the retainedKeys no longer have a unique key for the Activity, return true, and the process ends.

    However, if the queue is empty, the Activity is not considered recyclable yet, so manually triggering the GC will most likely ensure that the garbage collector has already handled it (because it really doesn’t need to collect, not because it didn’t for some reason). Then again call removeWeaklyReachableReferences method to determine whether a queue queue is empty, if is empty, memory leaks LeakCanary concluded that the Activity occurred, . Through the android OS. Debug. DumpHprofData method to the current pile of information, in the final analysis of information reference chain.

Improved LeakCanary

From the above principle analysis, we can see that LeakCanary has some problems, and we can improve it. The improvement measures are as follows:

  • Make sure they have the gc

    LeakCanary will automatically call the GC interface, and when the final VIRTUAL machine does not have a real GC, if not, then the ReferenceQueue queue is empty, it will be judged as a memory leak, so it may be misjudged, so we can add a “sentinel”, as long as it is reclaim, The virtual machine must have gc, if not, the virtual machine has not gc, the code is as follows:

    final WeakReference<Object> sentinelRef = new WeakReference<>(new Object()); triggerGc(); if (sentinelRef.get() ! = null) { // System ignored our gc request, we will retry later. MatrixLog.d(TAG, "system ignore our gc request, wait for next detection."); return Status.RETRY; }Copy the code

    As you can see, the implementation is simple: Just new an Object, trigger the GC, and determine whether the Object has been reclaimed.

  • Many times to determine

    Let’s assume that there is a situation where LeakCanary can be checked several times, such as 3 times, when determining whether or not an object has leaked memory and the object is referenced by the local variable.

  • Remind to heavy

    If an object is determined by LeakCanary as a memory leak, it will be notified. If it is determined several times, it will be notified several times, which is not a good experience. We can record the leaked object class name and only remind it once.

Wechat open source performance detection framework Matrix, has made improvements to LeakCanary, the improvement method is basically the same as the above points, the specific details can be analyzed by themselves.

supplement

  1. For the queue of WeakReference, when the associated object is only referenced by WeakReference, the WeakReference object (note that it is not the strong reference object associated) will be queued to queue.

  2. When queue is empty for the first time, gc is triggered manually as follows:

    public interface GcTrigger { GcTrigger DEFAULT = new GcTrigger() { public void runGc() { Runtime.getRuntime().gc(); this.enqueueReferences(); System.runFinalization(); } private void enqueueReferences() { try { Thread.sleep(100L); } catch (InterruptedException var2) { throw new AssertionError(); }}}; void runGc(); }Copy the code

    The garbage collector will try to perform garbage collection, but not necessarily.

  3. The dumpHprofData method can only dump the memory information of the current process, so for multi-process applications, LeakCanary needs to monitor every process.

  4. Analyzing memory information to obtain reference chains is implemented through the open source library HAHA.