当前位置: 首页 > 工具软件 > LeakCanary > 使用案例 >

LeakCanary的原理初探

霍鸣
2023-12-01

前言:

LeakCanary是很早之前的大名鼎鼎的square推出的一款安卓排查内存泄漏的工具。其功嫩相当强大,还记得19年的时候刚刚接触这款工具时,我们之前花了一整天找到的内存泄漏问题,用这款工具只用了大约十分钟就顺利的定位到了问题原因。

LeakCanary主要分为两块内容,第一块是发现排查内存泄漏问题,第二块是根据内存的状态输出泄漏的堆栈。本文主要讲解第一块内容,后续有时间会把第二块内容也补上。

PS:目前LeakCanary已经更新到2.8.1的版本,但是新版本代码结构较为复杂。所以本文还是机基于1.6.3的源码进行分析。

项目地址:https://github.com/square/leakcanary

、原理简介

LeakCanary的核心原理是在application中,注册所有activity的生命周期回调。当一个activity调用destory方法的时候,生成一个弱引用对象,然后加入到ReferenceQueue中。当一个对象如果可以被释放,那么其弱引用对象就会加入到ReferenceQueue中。

我们只要多次检查ReferenceQueue中是否存在这个activity的弱引用对象,就可以知道这个activity是否被泄漏了。

三、原理分析

3.1 两个LeakCanary类

其实LeakCanary有两个实现类,第一个在leakcanary-android的library中,第二个在leakcanary-android-no-op的library中,区别很简单。第二个里面是空实现,LeakCanary是不推荐线上使用的,所以写了一个空实现,release包时使用。

3.2 LeakCanary注册

 protected void setupLeakCanary() {
    enabledStrictMode();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
  }

我们项目中有可能有多个进程,我们只要对对主进程进行检查即可。

3.3 何时开启的回调注册

1.上述调用install后,会调用到AndroidRefWatcherBuilder的buildAndInstall()方法。

这里有两个参数watchActivities和watchFragments,分别对activity和fragment进行泄漏检查。

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

2.这里以Activity的注册为例,ActivityRefWatcher.install方法中,注册了所有activity的生命周期回调。

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

3.4 activity在destroy的时候如何去检查的

1.首先,会通过RefWatcher的watch方法开启检查。

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

2.watch方法中,首先会针对activity生成一个弱引用对象。key值是随机生成的,主要是为了识别唯一的一个activity弱引用对象。

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

3.ensureGoneAsync中,利用线程池中的线程,开启对内存泄漏的检查。这也是自然,毕竟不能在主线程进行检查。

这里的watchExecutor可以由外部传入,经典的组合模式。watchExecutor的不同,最终的组合逻辑就有区别了。可以满足不同的场景需求。

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

4.ensureGone方法是整篇文章的核心。所以这里会拆成好多步讲解

 @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ...
    removeWeaklyReachableReferences();//第一步

    ...
    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;
  }

5.第一步,removeWeaklyReachableReferences方法。前文讲到了ReferenceQueue的属性,一个弱引用对象如果可以被释放,那么就会加入到这个队列中。我们遍历queue队列,如果存在,则加入到retainedKeys这个HashSet中。retainedKeys中保存了所有符合被释放条件的activity的弱引用对象的key。

private void removeWeaklyReachableReferences() {
    // 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;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

6.第二步,使用gone方法检查是否符合被释放条件。

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

7.第三步,尝试同志虚拟机进行一次垃圾回收操作。

我们知道,Java是由虚拟机控制的回收机制,所以一个对象复合了回收条件后,并不一定是会被立马释放掉的,同样的,也不会立马去检查是否符合释放条件。所以这时候就需要我们主动通知一下虚拟机去进行对应的操作。

这里也是经典的组合模式,触发runGc的对象也是可以被传入的。

public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perform a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      System.runFinalization();
    }

Runtime.getRuntime().gc();//是通知虚拟机进行gc操作的。

enqueueReferences方法其实只是sleep了当前线程100毫秒,毕竟垃圾回收不是一下子就可以完成的。

System.runFinalization();  //强制调用已经失去引用的对象的finalize方法 

8.第四步,再次调用removeWeaklyReachableReferences方法检查是否符合释放条件。

9.第五步,再次调用gone进行判断。

10.第六步,如果这时候还没有符合被释放的条件,那么就说明这个activity有可能被泄漏了,这时候就要获取一下当前的内存堆栈信息,调用分析工具进行分析了。

 类似资料: