LeakCanary详解

LeakCanary简介

leakCanary是square公司推出的一个用于检测内存泄漏的工具,在一个activity完全ondestroy方法执行时,我们都会希望它的内存空间能够完全被回收。但是实际上并非是这样的,往往可能会出现内存泄漏,比如说这个activity被其他没有回收的类所持有引用。那么就会造成ondestory方法执行完之后,这个activity没有被回收,造成内存泄漏。所以,为了让我们在开发测试时更容易的发现内存泄漏的情况,square公司推出了这个开源库。

LeakCanary流程

LeakCanary的使用

对于一个app引入leakCanary这个库是非常的简单的,只需要在gradle文件中引入下面几个依赖,这几个依赖会针对不同的编译版本引入不同的包:

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
 }

然后在代码中,我们只需要在我们项目中的application类去调用install方法建立LeakCanary监测就行了。

  if (LeakCanary.isInAnalyzerProcess(this)) {
        //这个是在分析heap的进程中,监测进程不能和分析进程在同一个。
    return;
    }
    LeakCanary.install(this);

LeakCanary的简单实现流程

在我们在Application类中调用了LeakCanary的install方法之后,就开始整个activity的监测流程。

  1. 对application注册了一个关于activity生命周期的回调callBack,在这个回调里面,LeakCanary只关注onDestroy方法,毕竟我们需要监测内存泄漏,肯定是从onDestroy方法开始的。
  2. 一旦一个activity执行了ondestroy方法,也就是这个activity需要被销毁时,此时就开始了LeakCanary的工作,会在callBack里面执行watch方法,也就是开始监测了这个activity被销毁的过程。
  3. 在watch方法里面,会生成一个带有自定义key值的弱引用,LeakCanary通过这个弱引用来监测是否被完全回收,对于android里面的弱引用的回收,当弱引用指向的对象被gc回收时,此时会将这个弱引用添加到一个对应的队列里面。
  4. 假如watch监测出来可能出现内存溢出,此时就会利用一个叫做haha的库,这个库是回来解析heapDump文件的,然后通过这个文件分析是否真正的出现了内存溢出。出现溢出,生成溢出堆栈信息。

LeakCanary的代码实现:

RefWatcher的内部实现

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

在我们在application调用了install方法时,将会通过builder方法创建RefWatcher。

  • DisplayLeakService是一个服务类,主要是用来分析泄漏的结果
  • AndroidExcludedRefs排除android系统的bug 我们先看buildAndInstall的实现
 public RefWatcher buildAndInstall() {
    ...
    //创建RefWatcher实例
    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;
  }

在这个方法中,会创建出RefWatcher,同时开启Activity和Fragment的泄漏的监听。
首先,我们先看看如何开启Activity的监听。

public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

其中,activityRefWatcher.lifecycleCallbacks为

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

所以,对于Activity的监听,其实就是对application注册了LifeCycleCallback,在每一个watcheActivity的时候,都会移除上一次的注册。

 public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

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

最后在onActivityDestroyed的回调,执行watch操作,进行内存泄漏的检测。
其实fragment和activity的监听其实是非常类似的,只不过在onDestory回调到时,调用了RefWatcher的watchFragments方法

 private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity);
            }
          }
        };

在这个watchFragments中,注册了fragment的监听。

@Override public void watchFragments(Activity activity) {
    if (activity instanceof FragmentActivity) {
      FragmentManager supportFragmentManager =
          ((FragmentActivity) activity).getSupportFragmentManager();
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
    }
  }

这个fragmentLifecycleCallbacks是fragment的lifeCycleCallback

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {
        @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

这样,也就是完成了对于fragment的监听,在onFragmentDestroyed调用时,也会调用watch方法进行检测内存泄漏,只不过和activity不一样的是,在这里传入的fragment实例。

对于RefWatcher,这个类是LeakCanary的核心。我们可以先看看其中的一些成员变量

  //执行内存泄漏的检测
  private final WatchExecutor watchExecutor;
  //用于查询是否在查询中
  private final DebuggerControl debuggerControl;
  //处理Gc,执行gc操作
  private final GcTrigger gcTrigger;
  //Dump内存泄漏的堆文件
  private final HeapDumper heapDumper;
  private final HeapDump.Listener heapdumpListener;
  private final HeapDump.Builder heapDumpBuilder;
  //产生内存泄漏的key
  private final Set retainedKeys;
  //引用队列
  private final ReferenceQueue queue;
 
 

在catch方法执行检测

//这个方法会在任意一个activity的ondestroy方法调用时调用
public void watch(Object watchedReference, String referenceName) {
    //通过这个Disable参数可以仅在debug包中开启。
    if (this == DISABLED) {
      return;
    }
    //引用和引用名称不能为空
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    //记录监测的开始时间
    final long watchStartNanoTime = System.nanoTime();
    //生成一个随机的key,保证每次生成的弱引用的key都是不同的
    String key = UUID.randomUUID().toString();
    //添加到维护的key数组里面去,这是一个写时复制的数组,这个数组是用来记录LeakCanary生成的弱引用所对应的key,也就是记录监测的activity的key
    retainedKeys.add(key);
    //在这里生成弱引用,这个是LeakCanary自定义的,添加了key和引用名称,这个queue便是弱引用指向的对象被正确回收时会被添加到的队列。
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //开始监测这个引用的回收
    ensureGoneAsync(watchStartNanoTime, reference);
  }

上面的方法,除了最后一句话,其他的都是在做一些初始化操作而已,ensureGoneAsync这个方法才是执行的监测的入口,这个是一个同步方法,它会利用watchExecutor去顺序的执行每一个弱引用的回收确认:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    //excutor监测池,持续监测这个弱引用是否被回收
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

通过了run方法的执行,开始通过ensureGone方法去确认每一个弱引用的回收,在阅读这个关键的方法时,我们需要先来了解两个方法removeWeaklyReachableReferences和isGone:

private void removeWeaklyReachableReferences() {
    //这个会发生在finalization和垃圾收集器之前
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }
=====>
public Reference poll() {
        synchronized (lock) {
            //queue的头为空,表示没有弱引用所指向的对象被回收
            if (head == null)
                return null;

            return reallyPollLocked();
        }
    }
=====>
private Reference reallyPollLocked() {
        if (head != null) {
            //获取当前head头
            Reference r = head;
            //头等于尾,清空
            if (head == tail) {
                tail = null;
                head = null;
            } else {
                获取下一个元素
                head = head.queueNext;
            }
            //设置queueNext属性,标记一个引用有进队或者出队操作
            r.queueNext = sQueueNextUnenqueued;
            return r;
        }

        return null;
    }

这个removeWeaklyReachableReferences方法会从前面指定的队列去获取是否存在被回收的弱引用,假如存在,则把我们保存的key数组中移除这个被成功回收的key值,剩下的就是可能存在泄漏的弱引用的key值,知道了这个,下面看看isGone的判断就比较简单了

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

只需要判断retainedKeys数组中是否包含该引用的key就能知道有没有被回收了。下面来看看这这个ensureGone方法,看看是如何确认有没有造成内存泄漏的

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //通过回收队列的引用去移除我们的key数组
    removeWeaklyReachableReferences();
    
    if (debuggerControl.isDebuggerAttached()) {
      return RETRY;
    }
    //如果被标记的弱引用均已被回收,那么返回。
    if (gone(reference)) {
      return DONE;
    }
    //在这里进行二次确认,手动执行一次gc
    gcTrigger.runGc();
    //在gc完成之后,再此通过queue移除我们的key数组。
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      //仍然存在没被回收的弱引用,此时可能造成内存泄漏,我们需要通过heapDump文件来分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      //根据现在内存,生成dump文件
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        //目前不能读取,待重试状态
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //heapdumpListener分析这个heapDump文件,判断是否泄漏。
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

当有可能造成内存泄漏的时候,会通过heap内存文件分析是否真正的出现了内存泄漏,会将这个文件的分析转交给分析类去处理,而Watcher在此时就完成了自己的工作。
所以,watcher类主要做的事情就是在每一个ondestory调用时定义一个相对应的弱引用,并在gc后分析queue和key数组,判断是否可能造成泄漏,在有可能造成泄漏的时候,将dump文件的分析转交给其他类去处理。

Dump

我们看看dump文件分析的主要代码,首先watcher会通过listener将dump文件转交给监听,下面是这个listener的调用链:

heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
public void analyze(HeapDump heapDump) {
        Preconditions.checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
    }

HeapAnalyzeService是一个IntentService,它有自己的工作线程,在这个工作线程执行完毕之后,会关闭自身service。

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

工作线程的执行,

protected void onHandleIntent(Intent intent) {
        if(intent == null) {
            CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.", new Object[0]);
        } else {
            String listenerClassName = intent.getStringExtra("listener_class_extra");
            HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
            //将dump文件的分析交给了HeapAnalyzer类
            HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
            //获取泄漏的结果。
            AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
            AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
        }
    }

通过着listener会开启一个IntentService,intentService与普通的service最主要的区别就是拥有自己的工作线程,而且待工作线程执行完毕之后,会自己关闭service,不用我们去手动进行控制。在HeapAnalyzerService中,又通过HeapAnalyzer去分析弱引用。我们来看看checkForLeak这个方法:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
      ...
      //heap文件是.hprof后缀格式的
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //获取解析器,这个解析器库haha所提供的
      HprofParser parser = new HprofParser(buffer);
      //生成此时的内存快照
      Snapshot snapshot = parser.parse();
      
      //复制gc的根对象,这个root是用来判断可达性的,从root到我们的弱引用所指向的对象,
      deduplicateGcRoots(snapshot);

      //寻找泄漏的引用
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }

      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    }

首先会生成内存快照,然后通过此时的快照来获取所有的根节点,通过这个根节点来判断弱引用指向对象的可达性。我们看看寻找泄漏引用的逻辑:

private Instance findLeakingReference(String key, Snapshot snapshot) {
    //通过自定义弱引用对象的name获取该对象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List keysFound = new ArrayList<>();
    //获取当前的实例列表
    for (Instance instance : refClass.getInstancesList()) {
      //获取实例的参数域
      List values = classInstanceValues(instance);
      //获取key值
      String keyCandidate = asString(fieldValue(values, "key"));
      if (keyCandidate.equals(key)) {
        //如果需要寻找的key和获取出来的对象的key值一致,表示寻找到了这个弱引用,即有泄漏。
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

如果返回的不为空,那么返回寻找到的泄漏的弱引用对象。

你可能感兴趣的:(LeakCanary详解)