“This article has been posted on my CSDN blog in 2019, LeakCanary Principles from 0 to 1, so I migrate here to review it”

In order to make the article as easy as possible. Before exploring LeakCanary, it’s worth adding a few Java references.

Reference classification

Strong reference

Strong references are the most commonly used references. An object with a strong reference will not be reclaimed when GC occurs. When the Jvm virtual machine runs out of memory space, the virtual machine throws an OutOfMemoryError and does not reclaim objects with strong references to resolve the memory shortage problem.

Soft references

If an object has only soft references, the garbage collector will not reclaim the object if the VM has sufficient memory space. If memory space is insufficient, these soft reference only objects will be reclaimed in the next GC. If the garbage collector does not collect it, the object can be used by the program. Soft references can be used to implement memory-sensitive caching.

When creating a soft reference instance, you can pass in a ReferenceQueue to associate the soft reference with the ReferenceQueue, so that the Jvm virtual machine will add the soft reference to the associated ReferenceQueue before the object referenced by the soft reference is collected by the garbage collector.

A weak reference

The difference between a weak reference and a soft reference is that objects with only weak references have a shorter life cycle. When GC occurs, if an object has only weak references, its memory is reclaimed regardless of whether the virtual machine has enough memory space.

When creating a weak reference instance, you can pass in a ReferenceQueue to associate the soft reference with the ReferenceQueue, so that the Jvm virtual machine will add the soft reference to the associated ReferenceQueue before the object referenced by the soft reference is collected by the garbage collector.

Phantom reference

Virtual references do not determine the life cycle of the object. If an object holds only virtual references, it is just as likely to be collected by the garbage collector at any time as if there were no references at all.

When creating a virtual reference instance, you can pass in a ReferenceQueue (ReferenceQueue) to associate the soft reference with the ReferenceQueue, so that the Jvm virtual machine will add the soft reference to the associated ReferenceQueue before the object referenced by the soft reference is collected by the garbage collector.

Constructors for soft, weak, and virtual references can all pass a ReferenceQueue associated with them. After the ReferenceQueue is reclaimed, the reference itself will be added to the ReferenceQueue. The ReferenceQueue object reference.get() has been reclaimed. Reference.get () is null in this case.

Therefore, when an object referenced by a non-strong reference is reclaimed, if the reference is not added to the associated ReferenceQueue, then the object referenced by the reference has not been reclaimed. If it is determined that a non-strong reference to an object should appear in the ReferenceQueue, but does not, then the object sends a memory leak.


The theory basis for

When an Activity’s onDestory method is executed, it indicates that the Activity’s life cycle has ended, and the Activity object should be reclaimed when the next GC occurs.

  • By learning about citations above, consider the followingonDestoryOccurs when creating a weak reference refers to the R directionActivity, and associate aRefrenceQuencewhenActivityThe weak reference instance itself should appear at theRefrenceQuence, otherwise you can judge theActivityA memory leak exists.
  • throughApplication.registerActivityLifecycleCallbacks()Method can be registeredActivityLife cycle of listening, every time aActivitycallonDestroyWhen you do page destruction, go get thisActivityAnd associate a weak reference toReferenceQuence, passed the testReferenceQuenceDetermines whether the weak reference exists inActivityWhether the object is reclaimed properly.
  • whenonDestoryAfter being called, the initial observationActivityHas not beenGCWhen the collection is normal, it is manually triggered onceGCBecause ofManually initiateGCGarbage collection is not performed immediately after the requestTherefore, you need to confirm again after a certain delayActivityIf yes, check againActivityObject is not reclaimedActivityA memory leak exists.

The source code parsing

  1. Use the following method after importing the dependencyLeakCanaryforActivityMemory leak analysis:
public class MyApp extends Application {
   public void onCreate(a) {
       // Determine whether it is in the main process
       if (LeakCanary.isInAnalyzerProcess(this)) {
           // This process is dedicated to LeakCanary for heap analysis.
           // You should not init your app in this process.
       / / use LeakCanary
       LeakCanary.install(this); }}Copy the code

LeakCanary 2.0’s initialization is in its own ContentProvider:

The ContentProvider onCreate is called at a time between attachBaseContext and onCreate, LeakCanary 2.0 puts the initialization of LeakCanary in the onCreate function of its own ContentProvider. Setting MultiProcess to false ensures that the ContentProvider only initializes once. LeakCanary is also initialized only once

  1. Enter theLeakCanary#install(Application application)
public final class LeakCanary {

  * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
  * references (on ICS+).
 public static RefWatcher install(Application application) {
   return refWatcher(application)
   	   .listenerServiceClass(DisplayLeakService.class)// Display the online leak information after the memory leak
       .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())/ / white list

 /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
 public static AndroidRefWatcherBuilder refWatcher(Context context) {
   return newAndroidRefWatcherBuilder(context); }}Copy the code
  • ininstall(Application application)Inside, throughrefWatcherI created aAndroidRefWatcherBuilderObject.
  • You can tell by the name that this is abuilderPattern, here are some tips for reading the code for this design pattern:The basic thing to do before the build() method call is to do some variable assignment, just pay attention to the object that build() returnsSo I’m going to focus herebuildAndInstall()The call.
  1. in-depthAndroidRefWatcherBuilder#buildAndInstall()
  • AndroidRefWatcherBuilderinheritanceRefWatcherBuilderIn thebuildAndInstall()Create aRefWatcher(citing the prospector) instance and using static methodsActivityRefWatcher#installOnIcsPlus()willApplication.ActivityLifecycleCallbacksObject registration toApplicationObject, whenever there isActivitycallonActivityDestroyedMethod, the program calls backRefWatcherthewatch(Activity activity)Methods.
  • Memory leak judgment, analysis and leak information display are inRefWatcher#watch(Activity activity)To complete.
  * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
 public RefWatcher buildAndInstall(a) {
   	RefWatcher refWatcher = build();// Build the reference prospector
   	ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
   return refWatcher;
 //: ActivityRefWatcher.java
@TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {

 public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
     // If you need to support Android < ICS, override onDestroy() in your base activity.
   ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
   // Complete the registration of the Activity lifecycle

 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
     new Application.ActivityLifecycleCallbacks() {
       @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Override public void onActivityStarted(Activity activity) {}@Override public void onActivityResumed(Activity activity) {}@Override public void onActivityPaused(Activity activity) {}@Override public void onActivityStopped(Activity activity) {}@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Override public void onActivityDestroyed(Activity activity) {
         ActivityRefWatcher.this.onActivityDestroyed(activity); }};private final Application application;
       private final RefWatcher refWatcher;

        * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
        * after they have been destroyed.
       public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
         this.application = checkNotNull(application, "application");
         this.refWatcher = checkNotNull(refWatcher, "refWatcher");
       void onActivityDestroyed(Activity activity) {
        public void watchActivities(a) {
   		// Make sure you don't get installed twice.

 		public void stopWatchingActivities(a) { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); }}Copy the code
  1. RefWatcher#watch(Activity activity)Analysis of the
  • The above mentioned memory leak judgment, analysis and leak information display are inRefWatcher#watch(Activity activity)Complete, so you have to go to this method to find out.

Before the official start of the source code is necessary to explain:

  1. KeyedWeakReferenceisWeakReferenceA subclass of, which is used to hold aActivityWeak references and for eachActivityThe instance is bound to a globally uniqueKey(The specific method is to callUUID.randomUUID().toString();)
  2. ReferenceQueueIt’s a queue
public final class RefWatcher {

 public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();

 private final WatchExecutor watchExecutor;
 private final DebuggerControl debuggerControl;
 // Triggers the GC manually
 private final GcTrigger gcTrigger;
 private final HeapDumper heapDumper;
 // The unique UUID container associated with each Activity
 private final Set<String> retainedKeys;
 // A reference queue, where weak references to an Activity are put when the Activity is reclaimed normally
 private final ReferenceQueue<Object> queue;
 private final HeapDump.Listener heapdumpListener;
 // The whitelist. Objects in the whitelist can effectively hold the Activity and avoid memory detection
 private final ExcludedRefs excludedRefs;

 RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
     HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
   this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
   this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
   this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
   this.heapDumper = checkNotNull(heapDumper, "heapDumper");
   this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
   this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
   retainedKeys = new CopyOnWriteArraySet<>();
   queue = new ReferenceQueue<>();

  * Identical to {@link #watch(Object, String)} with an empty string reference name.
  * @see #watch(Object, String)
 public void watch(Object watchedReference) {
   watch(watchedReference, "");

  * Watches the provided references and checks if it can be GCed. This method is non blocking,
  * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
  * with.
  * @param referenceName An logical identifier for the watched object.
 public void watch(Object watchedReference, String referenceName) {
   if (this == DISABLED) {
   checkNotNull(watchedReference, "watchedReference");
   checkNotNull(referenceName, "referenceName");
   final long watchStartNanoTime = System.nanoTime();
   String key = UUID.randomUUID().toString();
   final KeyedWeakReference reference =
       new KeyedWeakReference(watchedReference, key, referenceName, queue);

   ensureGoneAsync(watchStartNanoTime, reference);

 private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
   watchExecutor.execute(new Retryable() {
     @Override public Retryable.Result run(a) {
       returnensureGone(reference, watchStartNanoTime); }}); } Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
   long gcStartNanoTime = System.nanoTime();
   long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// Remove the UUID corresponding to KeyedWeakReference in Quene from retainedKeys

   if (debuggerControl.isDebuggerAttached()) {
     // The debugger can create false leaks.
     return RETRY;
   // If retainedKeys does not contain the corresponding UUID
   if (gone(reference)) {
     return DONE;
   // If the corresponding UUID exists in retainedKeys, initiate manual GC and sleep 100ms
   // Remove the UUID corresponding to KeyedWeakReference in Quene in retainedKeys again
   // If the UUID still exists in the retainedKeys, the memory leak and display information will start
   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);
     // The information shared after the memory leak is finally displayed and processed by Notification in DisplayLeakService#onHeapAnalyzed
         new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
             gcDurationMs, heapDumpDurationMs));
   return DONE;
// Whether the UUID corresponding to the weak reference has been removed
 private boolean gone(KeyedWeakReference reference) {
   return! retainedKeys.contains(reference.key); }private void removeWeaklyReachableReferences(a) {
   // WeakReferences are enqueued as soon as the object to which they >point to becomes weakly
   // reachable. This is before finalization or garbage collection has >actually happened.
   KeyedWeakReference ref;
   // Remove a weak reference object from the weak reference queue of the reclaimed object and remove the corresponding UUID in the retainedKeys
   while((ref = (KeyedWeakReference) queue.poll()) ! =null) { retainedKeys.remove(ref.key); }}}Copy the code

When the onDestory method of the Activity is called, LeakCanary will add a globally unique UUID to the retainedKeys of RefWatcher and create a KeyedWeakReference object for the Activityd. The UUID is written into the KeyedWeakReference instance, and the KeyedWeakReference queue is associated with the reference queue, so that when the Activity object is normally reclaimed, the weak reference object will enter the queue. Get the KeyedWeakReference object ref in queue through loop traversal, take out the UUID in ref, and remove the UUID in retainedKeys. If the UUID of the weak reference still exists in the retainedKeys after the traversal is complete, the Activity object was not properly reclaimed after the onDestory call. At this point, the GcTrigger is used to initiate a GC manually, wait another 100ms, and then determine whether the Activity is reclaimed normally. If it is not reclaimed, the memory leak and display information will start.


RefWatch can be used to monitor any common object as follows: Monitor whether demoInstacne is properly recalled where it needs to be recalled.

Copy the code