内存泄露检测神器 -- LeakCanary源码分析

鉴于笔者能力有限,如有疏漏错误之处,敬请原谅,本文只做抛砖引玉的作用
一、内存泄露介绍
内存泄露基本上都是由于不恰当的使用,当对象使用完了之后,还存在强引用,导致该释放的时候,没有释放,一直占用内存,我想是很多人会遇到的问题,一般的解决思路是生成hprof文件,再用mat等内存分析工具来查看,找到怀疑点,然后对照着源码再来验证。这里有一个很好的工具,可以在运行期间就能检测到内存泄露,并且输出最短路径,它就是检测内存泄露的神器LeakCanary。
二、leakcanary介绍
相关的使用方法可以参考:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/,里面有详细的介绍,使用也比较简单。
首先在自定义的Application中onCreate中调用下面即可
public void onCreate() {
super.onCreate();
enabledStrictMode();//开启严格模式
LeakCanary.install(this);
}
private void enabledStrictMode() {
if (SDK_INT >= GINGERBREAD) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}

上面的使用是不是非常简洁,基本不需要什么操作就可以直接使用,那么它是怎么实现的呢。


三、从源码角度分析leakcanary
我们从源码的角度来分析LeakCanary的原理,LeakCanary源码地址:https://github.com/square/leakcanary,下面来看一下源码的目录结构
leakcanary
|------ leakcanary-analyzer 内存泄露文件分析器
|------ leakcanary-android 内存泄露平台android相关的具体实现,比如生成hprof,调用内存泄露检测,调用内存泄露分析器,以及显示最短路径
|------ leakcanary-android-no-op 从名字可以知道没有做任何操作
|------ leakcanary-sample 一个使用的sample
|------ leakcanary-watcher 整个内存泄露检测框架的抽象实现,独立于android系统
|------ 其他
根据模块来分析一下,具体如何实现的
1.内存泄露的检测

上图是leakcanary-watcher主要接口的UML类图,比较粗糙。RefWatcherBuilder.build构造其他几个类,作为RefWatcher的构造参数最终生成RefWatcher。
具体过程描述一下,当需要查看某个对象是否存在内存泄露,调用RefWatcher.watch方法,然后调用RefWatcher.ensureGoneAsync来调用WatchExecutor.execute执行异步操作,该操作描述一下,首先确认该对象是否已经被释放,如果没有,再次调用GcTrgger.runGc进行垃圾回收,然后再次确认该对象是否已经被释放,如果没有,则调用HeapDumper.dumpHeap()方法,生成heapdump文件,最后交给分析器分析。
2.内存泄露分析
leakcanary-analyzer负责这一功能,内存泄露的核心类是HeapAnalyzer,流程比较简单,但是具体实现细节相当复杂,主要用到了一个库-com.squareup.haha用于分析hprof文件的,下面看一下核心代码流程。
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));
  }
}
描述一下流程:
(1).首先将内存泄露文件(hprof)直接映射到内存(映射到虚拟内存),也就是下面操作
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile)
(2).解析hprof文件,生成堆快照(Snapshot),也就是下面操作,这个是根据hprof文件格式来解析的,有兴趣可以自己研究一下。
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
(3).根据快照,和gc根节点的名称,去掉重复的gc根节点,也就是下面操作
deduplicateGcRoots(snapshot);
(4).根据关键字,找到泄露引用实例
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
(5).然后找到内存泄露的最短强引用路径
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
最后将结果返回。
3.内存泄露检测、分析还有显示的整个调用过程
我们看leakcanary-android这个工程,LeakCanary作为入口,调用install方法
public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
上述是一个链式调用,refWatcher(application)初始化AndroidRefWatcherBuilder(继承自RefWatcherBuilder),listenerServiceClass初始化dump listener即ServiceHeapDumpListener(实现HeapDump.Listener接口,当生成dump file时,将内存泄露文件扔给分析器的接口),其中DisplayLeakService(继承自AbstractAnalysisResultService)处理分析完之后的结果的,buildAndInstall方法调用了上述RefWatcherBuilder.build方法,接着在Application中注册监听Activity生命周期的监听类。
if (refWatcher != DISABLED) {
  ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
  LeakCanary.enableDisplayLeakActivity(context);
}
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
  if (SDK_INT < ICE_CREAM_SANDWICH) {
    // If you need to support Android < ICS, override onDestroy() in your base activity.
    return;
  }
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  activityRefWatcher.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);
      }
    };
当Activity调用destroy之后,会调用ActivityRefWatcher.this.onActivityDestroyed方法,开始监听Activity是否有内存泄露。
void onActivityDestroyed(Activity activity) {
  refWatcher.watch(activity);
}
开始了上述描述的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);
}
这里需要注意的是使用了KeyedWeakReference(继承自WeakReference)是一个弱引用,并且加入到了ReferenceQueue中,遍历的这个队列可以知道哪些对象是否被回收,代码中用了一个Set来管理队列的对象,当对象不存在,从set中去掉。
检测过程在上面已经简单描述过,我们看下代码
(1)初始化RefWatcher
public final RefWatcher build() {
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }

  ExcludedRefs excludedRefs = this.excludedRefs;
  if (excludedRefs == null) {
    excludedRefs = defaultExcludedRefs();
  }

  HeapDump.Listener heapDumpListener = this.heapDumpListener;
  if (heapDumpListener == null) {
    heapDumpListener = defaultHeapDumpListener();
  }

  DebuggerControl debuggerControl = this.debuggerControl;
  if (debuggerControl == null) {
    debuggerControl = defaultDebuggerControl();
  }

  HeapDumper heapDumper = this.heapDumper;
  if (heapDumper == null) {
    heapDumper = defaultHeapDumper();
  }

  WatchExecutor watchExecutor = this.watchExecutor;
  if (watchExecutor == null) {
    watchExecutor = defaultWatchExecutor();
  }

  GcTrigger gcTrigger = this.gcTrigger;
  if (gcTrigger == null) {
    gcTrigger = defaultGcTrigger();
  }

  return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
      excludedRefs);
}
(2)调用watch方法,上面有描述
(3)再次调用gc,生成dump file
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)) {
    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);
    heapdumpListener.analyze(
        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
  }
  return DONE;
}
(4)扔给分析器分析
ServiceHeapDumpListener的analyze方法
public void analyze(HeapDump heapDump) {
  checkNotNull(heapDump, "heapDump");
  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
HeapAnalyzerService的runAnalysis和onHandleIntent,最后扔给了HeapAnalyzer来分析处理
public static void runAnalysis(Context context, HeapDump heapDump,
    Class<? extends AbstractAnalysisResultService> 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.");
    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);
}
(5)将最终的结果DisplayLeakService生成notification
AbstractAnalysisResultService调用sendResultToListener将结果扔到onHeapAnalyzed处理,onHeapAnalyzed在DisplayLeakService中实现的。
用户点击通知,可以启动DisplayLeakActivity显示内容。

鉴于笔者能力有限,分析过于粗糙,无法将精华部分呈现给读者,读者可以自行去查看源码流程和查找相关文档。源码地址:https://github.com/square/leakcanary

发布了36 篇原创文章 · 获赞 11 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章