android 使用 LeakCanary 分析内存泄漏原理

LeakCanary 是 Square 公司基于 MAT 开源的一个工具,用来检测 Android App 中的内存泄露问题。官方地址:https://github.com/square/leakcanary

上一篇文章分析了 android 开发可能会出现的内存的泄漏的情况,本篇文章主要来看看LeakCanary 是如何去分析代码中可能出现内存泄漏的原因。
android 开发中可能会引起内存泄漏的情况

LeakCanary 的简单用法

首先,build.gradle 中配置依赖库;

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

Application 开启检测:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 注册 LeakCanary
        LeakCanary.install(this);

    }
}

在 Application 中调用 install 方法之后,就可以检测我们 app 运行时候的内存泄漏情况。

分析 LeakCanary.install(this) 方法做了什么?

接下来我们就该详细分析 install 方法主要做了什么,进入LeakCanary类中:

  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)// 开启线程监听内存泄漏信息
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 排除一些忽略内存泄漏的路径
        .buildAndInstall();
  }

查看 refWatcher 方法;

  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

  AndroidRefWatcherBuilder(Context context) {
    this.context = context.getApplicationContext();
  }

refWatcher 方法返回了 AndroidRefWatcherBuilder 实例,而 AndroidRefWatcherBuilder 构造方法初始化了上下问环境,接下来则是调用 AndroidRefWatcherBuilder 的相关方法;

接下来在调用 AndroidRefWatcherBuilder 的 listenerServiceClass 方法之前,先看下参数 DisplayLeakService 类;

public class DisplayLeakService extends AbstractAnalysisResultService {

  @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
  }
public abstract class AbstractAnalysisResultService extends IntentService {
        @Override protected final void onHandleIntent(Intent intent) {
            HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
            AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
            try {
                onHeapAnalyzed(heapDump, result);
            } finally {
                //noinspection ResultOfMethodCallIgnored
             heapDump.heapDumpFile.delete();
            }
      }

  }

DisplayLeakService 实际是一个 IntentService,开了一个线程去分析相关信息,这里需要知道的就是为了监听内存泄漏的信息;后面检测到内存泄漏,会继续详细分析此方法;

所以 listenerServiceClass 也是为了开启一个线程进行监听;

  public AndroidRefWatcherBuilder listenerServiceClass(
      Class listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

继续调用 excludedRefs 方法;

  /** @see ExcludedRefs */
  public final T excludedRefs(ExcludedRefs excludedRefs) {
    this.excludedRefs = excludedRefs;
    return self();
  }

  @SuppressWarnings("unchecked")
  protected final T self() {
    return (T) this;
  }

excludedRefs 方法是为了排除一些可以忽略内存泄漏的路径,例如系统的内存泄漏;

具体操作可以通过配置 AndroidExcludedRefs builder 的相关参数配置;

 public static final class BuilderWithParams implements Builder {

    private final Map> fieldNameByClassName =
        new LinkedHashMap<>();
    private final Map> staticFieldNameByClassName =
        new LinkedHashMap<>();
    private final Map threadNames = new LinkedHashMap<>();
    private final Map classNames = new LinkedHashMap<>();

    private ParamsBuilder lastParams;

    BuilderWithParams() {
    }

    @Override public BuilderWithParams instanceField(String className, String fieldName) {// 忽略的某个类中某个变量
      checkNotNull(className, "className");
      checkNotNull(fieldName, "fieldName");
      Map excludedFields = fieldNameByClassName.get(className);
      if (excludedFields == null) {
        excludedFields = new LinkedHashMap<>();
        fieldNameByClassName.put(className, excludedFields);
      }
      lastParams = new ParamsBuilder("field " + className + "#" + fieldName);
      excludedFields.put(fieldName, lastParams);
      return this;
    }

    @Override public BuilderWithParams staticField(String className, String fieldName) {// 忽略的静态变量
      checkNotNull(className, "className");
      checkNotNull(fieldName, "fieldName");
      Map excludedFields = staticFieldNameByClassName.get(className);
      if (excludedFields == null) {
        excludedFields = new LinkedHashMap<>();
        staticFieldNameByClassName.put(className, excludedFields);
      }
      lastParams = new ParamsBuilder("static field " + className + "#" + fieldName);
      excludedFields.put(fieldName, lastParams);
      return this;
    }

    @Override public BuilderWithParams thread(String threadName) {// 忽略某个线程
      checkNotNull(threadName, "threadName");
      lastParams = new ParamsBuilder("any threads named " + threadName);
      threadNames.put(threadName, lastParams);
      return this;
    }

    /** Ignores all fields and static fields of all subclasses of the provided class name. */
    @Override public BuilderWithParams clazz(String className) {// 忽略某个类
      checkNotNull(className, "className");
      lastParams = new ParamsBuilder("any subclass of " + className);
      classNames.put(className, lastParams);
      return this;
    }

    public BuilderWithParams named(String name) {
      lastParams.name = name;
      return this;
    }

    public BuilderWithParams reason(String reason) {
      lastParams.reason = reason;
      return this;
    }

    public BuilderWithParams alwaysExclude() {
      lastParams.alwaysExclude = true;
      return this;
    }

    @Override public ExcludedRefs build() {
      return new ExcludedRefs(this);
    }
  }

接下来,继续调用 buildAndInstall 方法;

 public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return refWatcher;
  }

RefWatcher 用来观察 Activity 的释放资源,并分析内存泄漏情况。LeakCanary.enableDisplayLeakActivity(context) 方法是提示内存泄漏的通知提醒。

再往下继续查看 ActivityRefWatcher.install((Application) context, refWatcher),这里是对内存是否泄漏判断的核心;

  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }

查看watchActivities 方法 :

 public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities(); // 只需要处理一次,先关闭之前的监听
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }

watchActivities 方法先取消注册之前的的监听方法,然后注册此次运行Activity的内存泄漏的观察。

接下来看下 lifecycleCallbacks 这个类:

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

ActivityLifecycleCallbacks 实则是对每个 Activity 的生命周期进行监听;

最后一行,onActivityDestroyed 的销毁方法中 ActivityRefWatcher 会调用 onActivityDestroyed 方法:

  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

当 Activity 销毁的时候,会调用到 refWatcher 的 watch 方法,解析这个方法之前,先去看下 refWatcher 这个类有哪些成员变量:

public final class RefWatcher {

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

  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final Set retainedKeys;
  private final ReferenceQueue queue;
  private final HeapDump.Listener heapdumpListener;
  private final ExcludedRefs excludedRefs;
}

WatchExecutor watchExecutor:查找检测内存泄露的对象
DebuggerControl debuggerControl:检测当前是否正在调试中
GcTrigger gcTrigger:调用gc方法
HeapDumper heapDumper:dump内存泄露产生的文件
SetretainedKeys:存储引用key(待检测或已经产生泄露)
ReferenceQueue queue:引用队列,存储待检测的弱引用
HeapDump.Listener heapdumpListener:HeapDumper的回调
ExcludedRefs excludedRefs:排除系统引起的内存泄露
boolean computeRetainedHeapSize:检测泄露时是否计算堆的大小,默认为false

接下来继续看 RefWatcher 类的 watch 方法:


private final Set retainedKeys;

 public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

 public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference"); // 检测 watchedReference 是否为 null
    checkNotNull(referenceName, "referenceName"); // 检测 referenceName 是否为 null
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString(); // 获取一个唯一Id
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference); // 执行 gc 操作
  }

watch 方法会生成一个唯一 ID,同时将唯一 ID 存入当 一个 Set 集合中,然后会根据相关信息创建一个 KeyedWeakReference 类弱引用,并与 RefWatcher 中的 quene 进行关联。我们知道对于弱引用,在下一次 gc 的时候会被清除掉,如果不能被清除,则有可能会产生内存泄漏的情况;

final class KeyedWeakReference extends WeakReference {
  public final String key;
  public final String name;

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

接下来,查看 ensureGoneAsync 方法;

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

 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)) { // 如果不包含key ,即 activity 的弱引用被删了。 则可以完全回收不必担心内存泄漏
      return DONE;
    }
    gcTrigger.runGc(); //  使用代码调用 GC,内存回收;
    removeWeaklyReachableReferences();
    if (!gone(reference)) {// 如果 activity 还存在弱引用中,则可能出现泄漏
      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);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

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

  private void removeWeaklyReachableReferences() { // 删除 RefWatcher 中的 KeyedWeakReference
    // 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) { // 取 KeyedWeakReference 弱引用对象
      retainedKeys.remove(ref.key); 
    }
  }

因为 KeyedWeakReference 和 RefWatcher 的 quene 进行了关联, ensureGone 方法先遍历 quene 进行删除,此时如果对应 Activity 的 KeyedWeakReference 被删除了,就没有内存泄漏情况;如果未被删除,而且再次代码进行 GC 内存回收操作,还不能删除,则认定此 Activity 因为相关引用无法被删除,则会发生内存泄漏的情况;

对上面先做一个小的总结,LeakCanary 运行到这里主要做了哪些工作?
1 创建一个 Refwatcher ,启动一个ActivityRefWatcher。
2 通过 ActivityLiftcycleCallbacks 把 Activity 的 ondestory 生命周期关联。
3 将 Activity 以及相关信息放入到 KeyedWeakReference 弱引用对象中,调用 Gc 内存回收,通过 KeyedWeakReference 能否被回收掉判断是否存在内存卸扣情况;

之后创建 HeapDump 对象,并传达监听信息;

接下里继续分析 heapdumpListener.analyze(...) 方法:

@Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

analyze 方法的实现类在 ServiceHeapDumpListener 中 Refwatcher 初始化第一步即提到过 ,继续执行 HeapAnalyzerService.runAnalysis 方法:

public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
  }

  @Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}

HeapAnalyzerService 是 IntentService 的子类,onHandlerIntent 方法主要做了一下事情:
1 创建 HeapAnalyzer 对象,这里 heapDump.excludedRefs 排除了系统引起的内存泄漏。
2 执行 heapAnalyzer.checkForLeak 方法查找内存泄漏信息。

HeapAnalyzer 明显是分析内存泄漏的类,进入 checkForLeak 方法:

  public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse();
      deduplicateGcRoots(snapshot);

      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }

      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

checkForLeak 这里看到如果 heapDumpFile 不存在会抛出异常。存在的情况下:
1 .hprof 转化成 Snapshot。
2 deduplicateGcRoots(snapshot) 去除重复的泄漏文件。
3 findLeakingReference 找出内存泄漏的引用和 findLeakTrace 找出内存泄漏的路径。

findLeakingReference 获取内存泄漏对象的方法:

private Instance findLeakingReference(String key, Snapshot snapshot) {
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {
      List values = classInstanceValues(instance);
      String keyCandidate = asString(fieldValue(values, "key"));
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

napshot.findClass(KeyedWeakReference.class.getName()) 通过查找弱引用的对象,然后通过遍历该弱引用的所有实例,当发现 key 值相等的,返回泄漏对象并存储到集合中。

findLeakTrace 方法获取内存泄漏对象的路径:


  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {

    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(since(analysisStartNanoTime));
    }

    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    String className = leakingRef.getClassObj().getClassName();

    // Side effect: computes retained size.
    snapshot.computeDominators();

    Instance leakingInstance = result.leakingNode.instance;

    long retainedSize = leakingInstance.getTotalRetainedSize();

    // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
    if (SDK_INT <= N_MR1) {
      retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

通过buildLeakTrace 方法 ShortestPathFinder 对象找到内存泄漏的路径,同时 retainedSize 表示内存泄漏的空间大小。

到这里就完成内存泄漏信息分析,并通过 leakDetected 方法将内存泄漏的结果信息反馈出来。

你可能感兴趣的:(android 使用 LeakCanary 分析内存泄漏原理)