leakCanary principle

1. Questions in the interview

General mid-to-high-level interviews will ask about performance optimization and memory optimization. When it comes to memory issues, they will definitely ask about memory leaksLeakCanary .

Then there must be a question: Do you know LeakCanarywhat the principle is?
You may also ask: Do you know the IdleLeakCanary mechanism used ?

2. Analyze the principle of LeakCanary

The integration of LeakCanary is very simple, add dependencies, and then mainly LeakCanary.install(this);
this line of code in the Application, if you don’t understand, see the document
LeakCanary

Then let's look directly at installwhat the method does.

  public static RefWatcher install(Application application) {
    return refWatcher(application)
        .listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

directly look at the last.buildAndInstall(),发现 install 就是构建了一个 RefWatcher 。

  public RefWatcher buildAndInstall() {
   ...
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (watchActivities) {
        // 检测Activity内存泄漏的方式
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        // 检测Fragment内存泄漏的方式
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

Let's choose an example of detecting Activity memory leaks, see the corresponding processingActivityRefWatcher.install(context, refWatcher);

  public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    // 创建一个 ActivityRefWatcher
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    // 从application的registerActivityLifecycleCallbacks接口中,获取到Activity的生命周期回调,并传给 refWatcher。
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

application.registerActivityLifecycleCallbacksHere is the key point, monitor the Activity life cycle, and then call it in onActivityDestroyedthe callbackrefWatcher.watch(activity)。

Then continue with:

private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue;
...
public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

public void watch(Object watchedReference, String 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);
  }

watchedReferenceThe activity passed
retainedKeysis a Set, which is used to record the key of each object added to the detection
queue: ReferenceQueue<Object> reference queue inherits
KeyedWeakReferenceWeakReference, saves key and name,
name is passed an empty string character, which can be ignored.

Let's look at KeyedWeakReference :

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

Here is an important knowledge point, which is not mentioned in many articles:

Weak references are used in conjunction with reference queues. If the object held by the weak reference is recycled, the Java virtual machine will add the weak reference to the reference queue associated with it. That is to say, if KeyedWeakReferencethe held Activityobject is recycled, it KeyedWeakReferencewill be added to the reference queue queue. Conversely, if the KeyedWeakReference,则表示 KeyedWeakReference held object contained in the reference queue queue has been recycled.

LeakCanaryJust use this principle.

Then, after the weak reference is created,  ensureGoneAsync()the method is called.

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

The implementation class of this  watchExecutor is  AndroidWatchExecutor, look at AndroidWatchExecutor#executethe method:

@Override public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      //主线程中执行 ensureGone() 任务
      waitForIdle(retryable, 0);
    } else {
      //子线程中执行 ensureGone() 任务
      postWaitForIdle(retryable, 0);
    }
  }

Why not directly analyze ensureGonethe method  , because here is a little knowledge point, look at waitForIdle() the method:

private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // 下面的代码,需要在主线程中调用
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        //延时任务
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

Looper.myQueue().addIdleHandlerA task will be added to a mIdleHandlerslist in the message queue of the main thread. When the handler cannot get the message in the message queue, that is, Handlerwhen it is idle, it will go to mIdleHandlersthe list to take out the task and execute it.

The main thread executes this task when it is idle. What exactly does it do?
postToBackgroundWithDelay, as the name suggests, background delay processing:

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

The delay time delayMillis here is a constant from the past, 5 seconds ,
that is, ensureGone() the method is executed in the background thread after the main thread is idle for 5 seconds.

Now that's the point, let's look at  ensureGone()the method:

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //注释1, 移除弱引用 并移除 Set中对应的 key
    removeWeaklyReachableReferences();
    //注释2,为true表示 reference 对应的key已经不存在了,即reference被成功回收了,也即没有发生泄露
    if (gone(reference)) {
      return DONE;
    }
    //注释3,否则 gone(reference) == false,表示 reference 对应的key还存在,即reference还没有被回收,可能是垃圾回收器没有及时回收,手动触发Gc
    gcTrigger.runGc();
    // 继续移除引用
    removeWeaklyReachableReferences();
    //注释4,如果此时 gone(reference) == false,表示 reference 还没有被回收,那就是内存泄漏了。
    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);
      // 注释5,接下来得到一个内存快照,并在 leakCanary 进程中,通过 第三方库 HAHA ,并结合 GC root,对得到的内存快照 .hprof 进行分析,最终得出导致内存泄漏的引用链。
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

How to judge a few steps of memory leak

1. removeWeaklyReachableReferences

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    // 当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
    // 如果引用队列中是空的,没有Activity对象被回收。
    // 如果引用队列不为空,则清空引用队列
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

Traverse the reference queue, if the queue is not empty, it means that the activity has been recycled, and remove the corresponding key in retainedKeys (if the reference is recycled, remove the key); otherwise, if the queue is empty, it means that no reference has been recycled.

2. gone(reference)

private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
}

It says "the key is removed when the reference is recycled", so if there is a corresponding key in retainedKeys, it means that the object has not been recycled.

3. gcTrigger.runGc();
ensureGoneThe method analyzed above is to execute after another 5 seconds when the main thread is idle after the Activity exits, so the object has not been recycled, which is not necessarily a memory leak. The object may not be referenced at this time, and is waiting for the next garbage collection , so manually trigger the GC, and then repeat the operations of 1 and 2. If the object is still not recycled, it means that there is a real memory leak.

4. After judging the memory leak
if (!gone(reference)) {...}
, dump the objects in the heap. Using HAHAthis library, use the reachability analysis algorithm, including analyzing which object caused the memory leak, and popping up notifications. These are not the focus of this article. If you are interested, you can go and see for yourself.

5. Next, get a memory snapshot, and in the leakCanary process, through the third-party library HAHA, combined with GC root, analyze the obtained memory snapshot.hprof, and finally get the reference chain that leads to the memory leak.

To summarize, answer the questions in the interview:

LeakCanaryWhat is the principle? (for Activity)

LeakCanaryBy monitoring the Activity life cycle, when Activity onDestroy, create a weak reference, bind the key to the current Activity, save the key in the set, and associate a reference queue, and then start to detect whether the memory is available after the main thread is idle for 5 seconds Leakage, specific detection steps:
1: Determine whether there is a reference to the Activity in the reference queue, if there is, it means that the Activity has been recycled, and the corresponding key in the Set is removed.
2: Determine whether there is the key of the Activity to be detected in the Set. If not, it means that the Activity object has been recycled and there is no memory leak. If there is, it only means that the Activity object has not been recycled, and it may not be referenced at this time, and it is not necessarily a memory leak.
3: Manually trigger GC, and then repeat operations 1 and 2 to determine if there is a real memory leak.

Do you know LeakCanarythe Idle mechanism in ?

When Activity onDestroy, LeakCanarythe detection task is not executed immediately, but the task is added to an idle task list in the message queue, and then when the Handler cannot get the message in the message queue, that is, when the main thread is idle, it will Go to the idle task list to take the task out and execute it.

Guess you like

Origin blog.csdn.net/m0_49508485/article/details/127777536
Recommended