LeakCanary(2)正文

序、慢慢来才是最快的方法

LeakCanary系列

LeakCanary(1)前传

背景

LeakCanary是Square的开源库,通过弱引用方式侦查Activity或Fragment对象的生命周期,若发现内存泄漏自动 dump Hprof文件,通过HAHA库得到泄露的最短路径,最后通过Notification展示。

简单说就是在在Activity对象onDestory的时候,新建一个WeakReference对象指向Activity对象,如果Activity对象被垃圾回收的话,WeakReference对象就会进入引用序列的ReferenceQueue。

所以我们只需要在Activity对象OnDestory之后去查看ReferenceQueue序列是否有该WeakReference对象即可。

第一次观察是Activity的onDestory5秒后,如果发现ReferenceQueue对来还没有WeakReference对象,就进入第二次观察,如果有了,就证明没有泄漏,第二次观察跟第一次观察相比区别在于会先进行垃圾回收,在进行ReferenceQueue序列的观察。
 

源码调用链

1.Application里面初始化

 private fun setupLeakCanary(): RefWatcher {
        return if (LeakCanary.isInAnalyzerProcess(this)) {
            RefWatcher.DISABLED
        } else LeakCanary.install(this)
    }

2.LeakCanary 的install方法里面是用AndroidRefWathcerBuilder的buildAndInstall

  //构建者模式,创建对象RefWatcher
  //参数listenerServiceClass:分析Hprof的service
  //参数AndroidExcludeRefs:过滤已知的内存泄漏问题
public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

3.AndroidRefWatcherBuilder的buildAndInstall方法。

构建RefWahcher对象,注册Anctivty和Fragment的监听器

  public RefWatcher buildAndInstall() {
    //只允许install一次
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    // 建造者模式的最后一步,构造对象
    RefWatcher refWatcher = build();

    //判断是否开启了 LeakCanary,没有开启默认会返回 DISABLED 对象
    if (refWatcher != DISABLED) {

      // 是否检测 Activity 的 内存泄漏,默认开启
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }

      // 是否检测 Fragment 的 内存泄漏,默认开启
      if (watchFragments) {

        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

4.ActivityRefWatcher.install

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

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }


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

    ActivityRefWatcher的静态方法install传入了当前的Application,然后注册了Activity生命周期的监听回调,ActivityLifcyclerCallbacks。里面只重写了Activity的销毁onActivityDestoryed()。在监听里面调用RefWatcher的watch方法。

5.FragmentRefWatcher.Helper.install

 public static void install(Context context, RefWatcher refWatcher) {
      List fragmentRefWatchers = new ArrayList<>();

      if (SDK_INT >= O) {
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }

      try {
        Class fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
        Constructor constructor =
            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
        FragmentRefWatcher supportFragmentRefWatcher =
            (FragmentRefWatcher) constructor.newInstance(refWatcher);
        fragmentRefWatchers.add(supportFragmentRefWatcher);
      } catch (Exception ignored) {
      }

      if (fragmentRefWatchers.size() == 0) {
        return;
      }

      Helper helper = new Helper(fragmentRefWatchers);

      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

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

FragmentRefWatcher.Helper 的静态方法 install 里同样会注册一个 ActivityLifecycleCallbacks 用于监听 Activity 生命周期中的 onActivityCreated 的创建完成的回调,在 Activity 创建完成后,会对这个 Activity 注册 Fragment 的生命周期监听器。

install 方法首先会判断系统是否大于等于 Android O, 如果是那么会使用 android.app.FragmentManager 进行注册,如果需要兼容 Android O 以下需要自行在依赖中添加对 leakcanary-support-fragment 组件的依赖,然后通过反射构造出SupportFragmentRefWatcher; 然后将fragmentRefWatchers所有监听器取出,在 Activity 创建完成后,添加 Fragment 的生命监听,主要关注 Fragment 的 onFragmentViewDestroyedonFragmentDestroyed 方法。

6.watch方法**

 public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    
    //省略
    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);
  }

首先生成一个UUID唯一的key,LeakCanary构造了一个带有key的弱引用对象,并将queue设置为弱引用对象的引用队列。

PS:为什么要创建一个带有唯一key弱引用对象?

很简单,假设CommonActivity发生了内存泄漏,系统执行GC操作是,肯定不会回收当前对象,这样WeakReference对象也不会被回收。如果启动了多了CommonActivity,分析Hprof文件时我们可以获取所有的CommonActivity对象,当年我们去检测其中某一个具体的Activity时,就会出现无法匹配。但是如果使用了带有 key 的 WeakReference 对象,发生泄露时泄漏时,key 的值也会 dump 保存下来,这样我们根据 key 的一一对应关系就能映射到某一个 Activity。

7.RefWatcher的ensureGoneAsync方法

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
     
    // watchExecutor 是 AndroidWatchExecutor的一个实例
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

ensureGoneAsync 方法构造了一个 Retryable 对象,并将它传给 watchExecutor 的 execute 方法。最红调用的是AndroidWatcherExecutor的execute方法

 
    /**
      *判断当前线程是不是主线程,如果是主线程直接执行waitForIDle方法
      *否则执行postWaitForIdle方法
      *
      */
@Override public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }

watchExecutor 是 AndroidWatchExecutor 的一个实例, AndroidWatchExecutor 的 execute 方法的作用就是判断当前线程是否是主线程,如果是主线程,那么直接执行 waitForIdle 方法,否则通过 Handler 的 post 方法切换到主线程再执行 waitForIdle 方法。


  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

waitForIdle 方法通过调用 addIdleHandler 方法,指定当主进程中没有需要处理的事件时,在这个空闲期间执行 postToBackgroundWithDelay 方法。

PS:此处也是使用了Handler的IdleHandler的回调。

postToBackgroundWithDelay 方法每次执行会指数级增加延时时间,延时时间到了后,会执行 Retryable 里的方法,如果返回为重试,那么会增加延时时间并执行下一次。

一直在重试的代码正式 ensureGone 方法。

8.RefWatcher的ensureGone

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    //移除所有弱引用可达对象
    removeWeaklyReachableReferences();

    // 判断当前是否正在开启USB调试,LeakCanary 的解释是调试时可能会触发不正确的内存泄漏
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }

    // 上面执行 removeWeaklyReachableReferences 方法,判断是不是监视对象已经被回收了,
    //如果被回收了,那么说明没有发生内存泄漏,直接结束
    if (gone(reference)) {
      return DONE;
    }

    // 手动触发一次 GC 垃圾回收
    gcTrigger.runGc();

    // 再次移除所有弱引用可达对象
    removeWeaklyReachableReferences();
    
    // 如果对象没有被回收
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      //heapDumper是AndroidHeapDumper,调用dumpHeap方法
      //使用 Debug 类 dump 当前堆内存中对象使用情况
      File heapDumpFile = heapDumper.dumpHeap();
        
      // dumpHeap 失败的话,会走重试机制
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

      
      // 将hprof文件、key等属性构造一个 HeapDump 对象
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();


      // heapdumpListener 分析 heapDump 对象
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }
8.1removeWeaklyReachableReferences
  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);
    }
  }

还记得我们在 refWatcher.watch 方法保存了当前监视对象的 ref.key 了么,如果这个对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,通过 poll 操作就可以取出这个弱引用,这时候我们从retainedKeys中移除这个 key, 代表这个对象已经被正常回收,不需要再被监视了。

8.2dumpHeap方法

public File dumpHeap() {
  // 生成一个存储 hprof 的文件
  File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
  // 文件创建失败
  if (heapDumpFile == RETRY_LATER) {
    return RETRY_LATER;
  }
  // FutureResult 内部有一个 CountDownLatch,用于倒计时
  FutureResult waitingForToast = new FutureResult<>();
  // 切换到主线程显示 toast
  showToast(waitingForToast);
  // 等待5秒,确保 toast 已完成显示
  if (!waitingForToast.wait(5, SECONDS)) {
    CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
    return RETRY_LATER;
  }
  // 创建一个通知
  Notification.Builder builder = new Notification.Builder(context)
      .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
  Notification notification = LeakCanaryInternals.buildNotification(context, builder);
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  int notificationId = (int) SystemClock.uptimeMillis();
  notificationManager.notify(notificationId, notification);

  Toast toast = waitingForToast.get();
  try {
    // 开始 dump 内存到指定文件
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    cancelToast(toast);
    notificationManager.cancel(notificationId);
    return heapDumpFile;
  } catch (Exception e) {
    CanaryLog.d(e, "Could not dump heap");
    // Abort heap dump
    return RETRY_LATER;
  }
}

9.heapdumpListener.analyze(heapDump)

heapdumpListener 是 ServiceHeapDumpListener 的一个对象,最终执行了HeapAnalyzerService.runAnalysis方法。

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

HeapAnalyzerService 继承自 IntentService,IntentService的具体原理我就不多做解释了。IntentService会将所有并发的启动服务操作,变成顺序执行 onHandleIntent 方法。

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }

    // 监听 hprof 文件分析结果的类
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);

    // hprof 文件类
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);


    // checkForLeak 会调用 haha 组件中的工具,分析 hprof 文件
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);

    // 将分析结果发送给监听器 listenerClassName
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

10.HeapAnalyzer的checkForLeak

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

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

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
        
      // 将 hprof 文件解析成 Snapshot
      //注意MemoryMappedFiledBuffer这个类
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();

      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);

      // 查找内存泄漏项
      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, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

hprof 文件的解析是由开源项目 haha 完成的。

11.HeapAnalyzer的findLeakingReference

查找内存泄漏项

 private Instance findLeakingReference(String key, Snapshot snapshot) {
   
    
    // 从 hprof 文件保存的对象中找到所有 KeyedWeakReference 的实例
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    if (refClass == null) {
      throw new IllegalStateException(
          "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
    }
    List keysFound = new ArrayList<>();

    // 对 KeyedWeakReference 实例列表进行遍历
    for (Instance instance : refClass.getInstancesList()) {

      // 获取每个实例里的所有字段
      List values = classInstanceValues(instance);
      
      // 找到 key 字段对应的值
      Object keyFieldValue = fieldValue(values, "key");
      if (keyFieldValue == null) {
        keysFound.add(null);
        continue;
      }
      String keyCandidate = asString(keyFieldValue);
        
      // 如果这个对象的 key 和 我们查找的 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);
  }

12.HeapAnalyzer的findLeakTrace

查找泄漏处的调用链

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

    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    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));
    }

    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

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

    long retainedSize;
    if (computeRetainedSize) {

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

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();

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

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

精简摘要

  • 1 LeakCanary.install(application);此时使用application进行registerActivityLifecycleCallbacks,从而来监听Activity的何时被destroy

  • 2 在onActivityDestroyed(Activity activity)的回调中,去检测Activity是否被回收,检测方式如以下步骤。

  • 3 使用一个弱引用WeakReference指向这个activity,并且给这个弱引用指定一个引用队列queue,同时创建一个key来标识该activity

  • 4 然后将检测的方法ensureGone()投递到空闲消息队列。

  • 5 当空闲消息执行的时候,去检测queue里面是否存在刚刚的弱引用,如果存在,则说明此activity已经被回收,就移除对应的key,没有内存泄漏发生。

  • 6 如果queue里不存在刚刚的弱引用,则手动进行一次gc

  • 7 gc之后再次检测queue里面是否存在刚刚的弱引用,如果不存在,则说明此activity还没有被回收,此时已经发生了内存泄漏,直接dump堆栈信息并打印日志,否则没有发生内存泄漏,流程结束。

参考

Android 开源库 #7 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

被问到:如何检测线上内存泄漏,通过 LeakCanary 探究!
快手KOOM高性能线上解决方案

04 | 内存优化(下):内存优化这件事,应该从哪里着手?

你可能感兴趣的:(源码分析,LeakCanary,android,源码分析)