LeakCanary 原理和用法简单总结

LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。

Java基础知识

在总结之前需要先了解一些Java的基础知识。

1. 强引用

通常可以认为是通过new出来的对象,即使内存不足,GC进行垃圾收集的时候也不会主动回收。

Object obj = new Object();

2. 软引用

在内存不足的时候,GC进行垃圾收集的时候会被GC回收。

Object obj = new Object();
SoftReference softReference = new SoftReference<>(obj); 
  

3. 弱引用

无论内存是否充足,GC进行垃圾收集的时候都会回收。

Object obj = new Object();
WeakReference weakReference = new WeakReference<>(obj); 
  

4. 虚引用

和弱引用类似,主要区别在于虚引用必须和引用队列一起使用。

Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue<>();
PhantomReference phantomReference = new PhantomReference<>(obj, referenceQueue); 
  

5. 引用队列

如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。

6. GC回收机制算法 - 可达性算法

可达性算法是目前Java虚拟机比较常用的GC回收机制算法。

主要原理是是通过GCRoots对象作为根节点,从根节点开始往下遍历,遍历的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则说明此对象是不可用的。

LeakCanary 集成和使用

首先需要配置build.gradle

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

然后在Application类中加入以下代码。

public class DevApp extends Application {

    @Override
    public void onCreate() {
        if (!LeakCanary.isInAnalyzerProcess(this)) {
            LeakCanary.install(this);
        }
        super.onCreate();
    }
}

LeakCanary默认监控Activity类,如果我们需要自定义监控对象,那么就需要在我们确定不需要某个对象之后,手动调用RefWatcher对象的watch方法进行监控。

public class DevApp extends Application {

    @Override
    public void onCreate() {
        LeakCanaryWatcher.initialize(this);
        super.onCreate();
    }
}


public class LeakCanaryWatcher {

    private static RefWatcher sRefWatcher;

    private LeakCanaryWatcher() {
        throw new IllegalStateException("Utility class");
    }

    public static void initialize(Application application) {
        if (!LeakCanary.isInAnalyzerProcess(application)) {
            sRefWatcher = LeakCanary.install(application);
        }
    }

    public static void watch(Object obj) {
        if (sRefWatcher != null) {
            sRefWatcher.watch(obj);
        }
    }
}

LeakCanary原理

首先从install方法入手,分析LeakCanary是如何监控内存泄漏的。

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

  /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. 
  */
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

可以看出LeakCanary使用了构建者模式去创建RefWatcher对象(实际上很多开源框架都使用了构建者模式去创建对象)。

listenerServiceClass方法是用来显示内存分析结果的,和原理联系不大。

excludedRefs则是排除了一些系统造成的内存泄漏,提高了检测精度。

下面我们分析buildAndInstall方法

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


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

  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(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);
        }
      };

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

代码很清晰,build方法很简单,就是对一些参数的配置,重点在install方法。向Application注册一个Activity的生命周期回调,并且在onActivityDestroyed回调中调用watch方法进行监控,这也是之前提到的LeakCananry框架默认监控Activity的原因。

LeakCanary核心的代码在watch方法中

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

retainedKeys是一个Set集合,queue就是之前提到的引用队列。这里使用UUID为监控对象生成了一个唯一的key,并将key放入Set集合,同时创建了一个监控对象的弱引用。

准备工作做完了,下面开始对对象进行内存泄漏分析。

  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) {
    .....
 
    removeWeaklyReachableReferences();

    // 如果是debug模式,不进行分析
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }

    // 如果Set集合里不包含监控对象,那么说明已经分析过了,不需要再次分析
    if (gone(reference)) {
      return DONE;
    }
    // 手动触发GC
    gcTrigger.runGc();
    
    removeWeaklyReachableReferences();
    // 如果Set集合仍然包含监控对象,说明监控对象发生了内存泄漏
    if (!gone(reference)) {
      .....
    }
    return DONE;
  }

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

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

启用了一个子线程去监控内存泄漏。

基本方法是手动触发GC,然后将引用队列里的元素出队列(GC后存在于引用队列说明被回收),从Set集合中将出队列的元素的key移除,如果移除后Set集合然后包含某对象的key值,说明该对象没有被回收,即发生了内存泄漏。

总结

1. 使用UUID为监控对象创建一个唯一的key值,并将key放入Set集合中。

2. 创建一个监控对象的弱引用,并开启线程进行内存分析。

3 手动触发GC,利用弱引用被回收会被放入引用队列的机制,将引用队列里的被回收对象出列,并从Set集合中移除对应的key值。

4 如果Set集合中监控对象的key值没有被移除,说明监控对象没有被放入引用队列,即没有被回收,发生了内存泄漏。

 

 

你可能感兴趣的:(android,开源框架)