全新版本LeakCanary2源码解析

2,257 阅读9分钟

这里是船新版本的LeakCanary源码,是兄弟就砍我!(狗头)

1. 前言

1.1 LeakCanary是什么?

我们先来看看官网上对它的介绍:

LeakCanary is a memory leak detection library for Android

可见LeakCanary是适用于Android内存泄露检测库。

1.2 LeakCanary可以解决什么问题?

  1. 内存泄漏一直是开发人员比较头疼的问题,因为他并不是一个Java异常,只有等到内存溢出的时候,我们才会去排查是否发生了内存泄漏的问题,
  2. 当我们分析内存泄漏的时候,会使用MAT去分析hprof文件,整个流程相对繁琐。

这个库的出现就是为了解决上面的问题,可以帮助开发人员大大减少OutOfMemoryError崩溃。

1.3 LeakCanary2的不同

LeakCanary2相对与LeakCanary发生了哪些变化呢?

  • 完全使用Kotlin重写
  • 泄漏类型分组
  • 使用了新的Heap分析工具Shark工具,放弃了之前的HAHA工具,新工具的内存比之前的要少90%,快6倍。

1.4 LeakCanary的使用

我们先来看看之前的LeakCanary是怎么使用的:

  1. 首先先引入库:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'

  1. 然后在自定义的Application中调用方法:
public class App extends Application {

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

那么现在的LeakCanary2的使用:

dependencies {
  // 使用debugImplementation是因为LeakCanary只能在debug版本使用。
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'}

这样就可以了,实现的方式更简单了。

LeakCanary的组成

LeakCanary组成的模块:

leakcanary-android

leakcanary-android-core

leakcanary-object-watcher

leakcanary-object-watcher-android

leakcanary-android-instrumentation

leakcanary-object-watcher-android-support-fragments

leakcanary-object-watcher-android-androidx

leakcanary-deobfuscation-gradle-plugin

leakcanary-android-process

shark

shark-test

shark-log

shark-hprof

shark-hprof-test

shark-graph

shark-cli

shark-android

2. LeakCanary原理

2.1 LeakCanary初始化

我们发现LeakCanary的实现更简单了,是怎么回事呢?让我们看看源码,在 leakcanary-object-watcher-android模块中的AppWatcherInstaller.kt

AppWatcherInstaller 是继承ContentProviderContentProvider的启动是要快于Application的加载的,所以重写了onCreate方法,在这里调用了install方法。

override fun onCreate(): Booleanval application = context!!.applicationContext as Application  InternalAppWatcher.install(application)
return true
}

AppWatcherInstaller会有两个子类:

  1. MainProcess

app的进程中使用LeakCanary

我们可以在leakcanary-object-watcher-android模块中的AndroidManifest.xml中看到:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.squareup.leakcanary.objectwatcher" >
<application> 
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
</application>
</manifest>

  1. LeakCanaryProcess

使用 leakcanary-android-process模块的时候,会在一个新的进程中去开启LeakCanary

leakcanary-android-process模块中的 AndroidManifest.xml中:

<manifest 
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary">
<application>
<service
android:name="leakcanary.internal.HeapAnalyzerService"
android:exported="false"
android:process=":leakcanary" />

<provider
android:name="leakcanary.internal.AppWatcherInstaller$LeakCanaryProcess"
android:authorities="${applicationId}.leakcanary-process.installer
android:process=":leakcanary"
android:exported="false"/>
</application>
</manifest>

可以看到这里启用了一个新的进程:leakcanary

需要注意的是,使用LeakCanaryProcess的时候,会关闭Watcher功能,因为在不同进程中,也无法观测App进程中的信息:

internal class LeakCanaryProcess : AppWatcherInstaller() {  

override fun onCreate(): Boolean {  
super.onCreate()   
AppWatcher.config = AppWatcher.config.copy(enabled = false)    return true  
  }
}

2.2 InternalAppWatcher#install()

我们现在进入到install()方法中去:

fun install(application: Application) {
  SharkLog.logger = DefaultCanaryLog() 
   SharkLog.d { "Installing AppWatcher" }  
  checkMainThread() 
if (this::application.isInitialized) {   
   return  
  } 
InternalAppWatcher.application = application 
val configProvider = { 
  AppWatcher.config 
 } 
ActivityDestroyWatcher.install(application, objectWatcher,configProvider) 

FragmentDestroyWatcher.install(application, objectWatcher,configProvider) 
onAppWatcherInstalled(application)}

可以看到主要做了两件事情,就是对ActivityFragment的注册观察,我们来对ActivityDestroyWatcher进行分析。

2.3 ActivityDestroyWatcher

进入到ActivityDestroyWatcher#install方法中:

 companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }

我们在这里调用了registerActivityLifecycleCallbacks注册activity的生命周期回调。

PS: 在Kotlin中,Application会有一个registerActivityLifecycleCallbacks这样的方法,当它被调用的时候,所有的Activity的生命周期都会被监控。

继续看下去:

override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }

CallBack的回调onActivityDestroyed方法中,将activity加入到观察列表中。

我们进入到 watch 方法中看看:

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

我们来看看removeWeaklyReachableObjects方法:

  private fun removeWeaklyReachableObjects() {

    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

我们来看看 KeyedWeakReference是什么?

class KeyedWeakReference(
  referent: Any,
  val key: String,
  val description: String,
  val watchUptimeMillis: Long,
  referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
    referent, referenceQueue
)

可以看到KeyedWeakReference 是继承 弱引用 WeakReference 的。 当GC发生的时候,弱引用都是会被回收掉的。

再看看看queue 是什么?

private val queue = ReferenceQueue<Any>()

实现了一个ReferenceQueue 的实例。

这个queue 就是用来存放被GC后的对象。在这里就是存放被GC过的activity对象。

再看看 watchedObjects

  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

就是用来保存弱引用的实例。

所以这个 removeWeaklyReachableObjects()方法就是将已经GC掉的对象从watchedObjects中移除掉。

继续看看 checkRetainedExecutor 是啥?

我们回到 InternalAppWatcher.kt中可以看到:

private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }
  ---------
 val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)

这里是使用了Handler来发送延迟消息,延迟5秒。

接着看看 moveToRetained 方法:

@Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

moveToRetained方法中还是先删除一次GC掉的对象,其他没有被GC的对象会被认为保留并通知下去。

所以watch方法的流程:

  1. 首先先将已经GC的对象移除出watchedObjects,也就是清理一次GC过的对象。
  2. 然后将需要观察的对象包装成 KeyedWeakReference,并放入到watchedObjects 中。
  3. 然后通过checkRetainedExecutor来调用 moveToRetained 通知。

2.4 onAppWatcherInstalled

InternalAppWatcher.kt#install中的最后还会调用 onAppWatcherInstalled,这是一个方法对象,它的赋值是在init中:

 init {
    val internalLeakCanary = try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
          .get(null)
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
    @kotlin.Suppress("UNCHECKED_CAST")
    onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
  }

上面这一段代码的意思是 通过反射获取 InternalLeakCanary.INSTANCE 这个单例对象。

转化为Java代码:

void invoke(Application) {
  
}

因为InternalLeakCanary 是属于leakcanary-android-core 模块,这就是为什么要使用反射了。

所以调用onAppWatcherInstalled这个方法对象就是调用他的invoke方法:


override fun invoke(application: Application) {
    this.application = application

    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    addDynamicShortcut(application)

    disableDumpHeapInTests()
  }

调用 addOnObjectRetainedListener 来注册一个监听,也就是moveToRetained的回调监听。

AndroidHeapDumper则是通过用Debug.dumpHprofData()方法从虚拟机中 dump hprof 文件。

override fun dumpHeap(): File? {
//......
Debug.dumpHprofData(heapDumpFile.absolutePath)
//.....
}

--------------------------

 public static void dumpHprofData(String fileName) throws IOException {
        VMDebug.dumpHprofData(fileName);
    }

GcTrigger通过调用Runtime.getRuntime().gc()方法触发虚拟机进行 GC 操作。

object Default : GcTrigger {
    override fun runGc() {
      Runtime.getRuntime() .gc()
      enqueueReferences()
      System.runFinalization()
    }

HeapDumpTrigger 是用来管理Heap Dump的。

onObjectRetained这个方法被回调的时候,触发Heap Dump的方法会被执行。

fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false
    )
  }

进入到 scheduleRetainedObjectCheck 中:

 private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
      SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
      return
    } else {
      val verb = if (rescheduling) "Rescheduling" else "Scheduling"
      val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
      SharkLog.d { "$verb check for retained objects${delay} because $reason" }
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }

继续进入到 checkRetainedObjects 中:

private fun checkRetainedObjects(reason: String) {
    val config = configProvider()
   
    if (!config.dumpHeap) {
      SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
      return
    }
    // 这里是 保留的对象 也就是watch方法中保留的对象
    var retainedReferenceCount = objectWatcher.retainedObjectCount
   // 保留的对象不为0的时候
    if (retainedReferenceCount > 0) {
      // 触发一次gc
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

   // 检测当前保留对象数量
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

   // 默认 debug 时不执行,重新安排到 20s 后
    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      onRetainInstanceListener.onEvent(DebuggerIsAttached)
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_debugger_attached
          )
      )
      scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    // 60s内不执行,会重新安排一次
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      scheduleRetainedObjectCheck(
          reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
          rescheduling = true,
          delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
    dismissRetainedCountNotification()
    
  // 执行 dump heap
    dumpHeap(retainedReferenceCount, retry = true)
  }

关键处都有注释,最终还是执行到了dumpHeap方法:

private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    val heapDumpFile = heapDumper.dumpHeap()
    if (heapDumpFile == null) {
      if (retry) {
  
        scheduleRetainedObjectCheck(
            reason = "failed to dump heap",
            rescheduling = true,
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
        )
      } else {
       
      }
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_dump_failed
          )
      )
      return
    }
    lastDisplayedRetainedObjectCount = 0
    lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }

可以看到先通过 heapDumper.dumpHeap() 来获取到heapDumpFile

然后通过objectWatcher.clearObjectsWatchedBefore 来移除之前的保留对象:

@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
    val weakRefsToRemove =
      watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
    weakRefsToRemove.values.forEach { it.clear() }
    watchedObjects.keys.removeAll(weakRefsToRemove.keys)
  }

最后调用HeapAnalyzerService.runAnalysis()进行分析。

所以ObjectWatcher来保存弱应用对象,HeapDumpTrigger 来触发heap dump,最后 HeapAnalyzerService来分析。

2.5 HeapAnalyzerService#runAnalysis

进入到runAnalysis方法:

fun runAnalysis(
      context: Context,
      heapDumpFile: File
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      startForegroundService(context, intent)
    }

其实也是启动了一个前台服务,HeapAnalyzerService是继承 IntentService,调用的时候会走 onHandleIntent 方法,

override fun onHandleIntent(intent: Intent?) {  onHandleIntentInForeground(intent)}

HeapAnalyzerService 会重写 onHandleIntentInForeground 这个方法:

override fun onHandleIntentInForeground(intent: Intent?) {
    if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
      SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
      return
    }

    // Since we're running in the main process we should be careful not to impact it.
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File

    val config = LeakCanary.config
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
  }

这里最终进入到 analyzeHeap 这个方法中:

private fun analyzeHeap(
    heapDumpFile: File,
    config: Config
  ): HeapAnalysis {
    val heapAnalyzer = HeapAnalyzer(this)

    val proguardMappingReader = try {
      ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
    } catch (e: IOException) {
      null
    }
    return heapAnalyzer.analyze(
        heapDumpFile = heapDumpFile,
        leakingObjectFinder = config.leakingObjectFinder,
        referenceMatchers = config.referenceMatchers,
        computeRetainedHeapSize = config.computeRetainedHeapSize,
        objectInspectors = config.objectInspectors,
        metadataExtractor = config.metadataExtractor,
        proguardMapping = proguardMappingReader?.readProguardMapping()
    )
  }

可以看到heapAnalyzer.analyze这个方法,它是用来计算泄漏对象到GC Roots的最短路径。

进入到analyze方法中,可以看到如下操作:

return try {
      listener.onAnalysisProgress(PARSING_HEAP_DUMP)
      Hprof.open(heapDumpFile)
          .use { hprof ->
            val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
            val helpers =
              FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
            helpers.analyzeGraph(
                metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
            )
          }
    } catch (exception: Throwable) {
      HeapAnalysisFailure(
          heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
          HeapAnalysisException(exception)
      )
    }

可以看到使用了 Hprof.open来解析hprof文件,然后这个文件会被HprofHeapGraph.indexHprof调用,用来生成 heap 中的对象关系图。

fun indexHprof(
      hprof: Hprof,
      proguardMapping: ProguardMapping? = null,
      indexedGcRootTypes: Set<KClass<out GcRoot>> = setOf(
          JniGlobal::class,
          JavaFrame::class,
          JniLocal::class,
          MonitorUsed::class,
          NativeStack::class,
          StickyClass::class,
          ThreadBlock::class,
          ThreadObject::class,
          JniMonitor::class
      )
    ): HeapGraph {
      val index = HprofInMemoryIndex.createReadingHprof(hprof, proguardMapping, indexedGcRootTypes)
      return HprofHeapGraph(hprof, index)
    }

indexedGcRootTypes 是一个GC Roots的集合,包括:

JniGlobal::class,

native中的全局变量

JavaFrame::class,

Java的局部变量

JniLocal::class,

native中的局部变量

MonitorUsed::class,

调用了wait()notify()方法 或者synchronized的对象

NativeStack::class,

native 中的入参和出参

StickyClass::class,

系统类

ThreadBlock::class,

活动线程引用的对象

ThreadObject::class,

活动线程

JniMonitor::class

未知,可能是 native 中的同步对象

在生成graph之后,我们可以通过它来获取泄漏对象的ID

FindLeakInput.analyzeGraph方法中:

val leakingObjectIds = 
leakingObjectFinder.findLeakingObjectIds(graph)

然后生成泄漏对象报告

val (applicationLeaks, libraryLeaks) = findLeaks(leakingObjectIds)

leakcanary定义了两种泄漏类型

  1. ApplicationLeak

app应用中发现的泄漏

  1. LibraryLeak

应用依赖库中的代码泄漏

进入到findLeaks 中:

private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    val pathFinder = PathFinder(graph, listener, referenceMatchers)
    val pathFindingResults =
      pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)

    SharkLog.d { "Found ${leakingObjectIds.size} retained objects" }

    return buildLeakTraces(pathFindingResults)
  }

我们继续进入到findPathsFromGcRoots中:

fun findPathsFromGcRoots(
    leakingObjectIds: Set<Long>,
    computeRetainedHeapSize: Boolean
  ): PathFindingResults {
    listener.onAnalysisProgress(FINDING_PATHS_TO_RETAINED_OBJECTS)

    val sizeOfObjectInstances = determineSizeOfObjectInstances(graph)

    val state = State(leakingObjectIds, sizeOfObjectInstances, computeRetainedHeapSize)

    return state.findPathsFromGcRoots()
  }

这里是查询泄露对象到 GC Roots 的路径,我们进入到state.findPathsFromGcRoots()中:

private fun State.findPathsFromGcRoots(): PathFindingResults {
    enqueueGcRoots()
   // 省略部分代码
    return PathFindingResults(shortestPathsToLeakingObjects,dominatedObjectIds)
  }

先看看enqueueGcRoots 做了哪些操作

  private fun State.enqueueGcRoots() {
  
 // 将 GC Roots 进行排序 
 // 排序是为了确保 ThreadObject 在 JavaFrames 之前被访问,这样可以通过 ThreadObject.threadsBySerialNumber 获取它的线程信息
    val gcRoots = sortedGcRoots()

    val threadNames = mutableMapOf<HeapInstance, String>()
    val threadsBySerialNumber = mutableMapOf<Int, Pair<HeapInstance, ThreadObject>>()
    gcRoots.forEach { (objectRecord, gcRoot) ->
      if (computeRetainedHeapSize) {
       //计算泄漏对象保存的内存size
        undominateWithSkips(gcRoot.id)
      }
      when (gcRoot) {
        is ThreadObject -> {
         // 保存活动线程的SerialNumber
          threadsBySerialNumber[gcRoot.threadSerialNumber] = objectRecord.asInstance!! to gcRoot
          // 加入队列
          enqueue(NormalRootNode(gcRoot.id, gcRoot))
        }
        is JavaFrame -> {
        // Java局部变量
          val threadPair = threadsBySerialNumber[gcRoot.threadSerialNumber]
          if (threadPair == null) {
            // Could not find the thread that this java frame is for.
            enqueue(NormalRootNode(gcRoot.id, gcRoot))
          } else {

            val (threadInstance, threadRoot) = threadPair
            val threadName = threadNames[threadInstance] ?: {
              val name = threadInstance[Thread::class, "name"]?.value?.readAsJavaString() ?: ""
              threadNames[threadInstance] = name
              name
            }()
            
// RefreshceMatchers 用于匹配已知的引用节点
// IgnoredReferenceMatcher 表示忽略这个引用节点 
// LibraryLeakReferenceMatcher 表示这是库内存泄露对象

            val referenceMatcher = threadNameReferenceMatchers[threadName]

            if (referenceMatcher !is IgnoredReferenceMatcher) {
              val rootNode = NormalRootNode(threadRoot.id, gcRoot)

              val refFromParentType = LOCAL
              // Unfortunately Android heap dumps do not include stack trace data, so
              // JavaFrame.frameNumber is always -1 and we cannot know which method is causing the
              // reference to be held.
              val refFromParentName = ""

              val childNode = if (referenceMatcher is LibraryLeakReferenceMatcher) {
                LibraryLeakChildNode(
                    objectId = gcRoot.id,
                    parent = rootNode,
                    refFromParentType = refFromParentType,
                    refFromParentName = refFromParentName,
                    matcher = referenceMatcher
                )
              } else {
                NormalNode(
                    objectId = gcRoot.id,
                    parent = rootNode,
                    refFromParentType = refFromParentType,
                    refFromParentName = refFromParentName
                )
              }
              enqueue(childNode)
            }
          }
        }
        is JniGlobal -> {
         // native 全局变量
          val referenceMatcher = when (objectRecord) {
            is HeapClass -> jniGlobalReferenceMatchers[objectRecord.name]
            is HeapInstance -> jniGlobalReferenceMatchers[objectRecord.instanceClassName]
            is HeapObjectArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
            is HeapPrimitiveArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
          }
          if (referenceMatcher !is IgnoredReferenceMatcher) {
            if (referenceMatcher is LibraryLeakReferenceMatcher) {
              enqueue(LibraryLeakRootNode(gcRoot.id, gcRoot, referenceMatcher))
            } else {
            
             // 入列 NormalRootNode
              enqueue(NormalRootNode(gcRoot.id, gcRoot))
            }
          }
        }
        // 其他 GC Roots入列 NormalRootNode
        else -> enqueue(NormalRootNode(gcRoot.id, gcRoot))
      }
    }
  }

这就是GC Roots节点入列的过程

enqueue方法中,出现了两种队列toVisitLastQueue 和 优先级更高的toVisitQueue,泄漏优先级低的会先存入到toVisitLastQueue 中,有如下3种情况:

  • LibraryLeakNode
  • ThreadObject
  • JavaFrame

这三种情况导致的泄漏比较少,所以放入到toVisitLastQueue中。

在将所有的 GC Roots节点入列后,使用广度优先遍历所有的节点,当访问节点是泄露节点,则添加到shortestPathsToLeakingObjects中。

val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
    visitingQueue@ while (queuesNotEmpty) {
      val node = poll()

      if (checkSeen(node)) {
        throw IllegalStateException(
            "Node $node objectId=${node.objectId} should not be enqueued when already visited or enqueued"
        )
      }

      if (node.objectId in leakingObjectIds) {
        shortestPathsToLeakingObjects.add(node)
        // Found all refs, stop searching (unless computing retained size)
        if (shortestPathsToLeakingObjects.size == leakingObjectIds.size) {
          if (computeRetainedHeapSize) {
            listener.onAnalysisProgress(FINDING_DOMINATORS)
          } else {
            break@visitingQueue
          }
        }
      }

      when (val heapObject = graph.findObjectById(node.objectId)) {
       // 将它的静态变量入列
        is HeapClass -> visitClassRecord(heapObject, node)
        // 将他的实例变量入列
        is HeapInstance -> visitInstance(heapObject, node)
        // 将非空元素入列
        is HeapObjectArray -> visitObjectArray(heapObject, node)
      }
    }

这样,我们就将所有的泄漏对象都查找出来了。

我们回到findLeaks方法中,在找到泄漏节点之后就调用了buildLeakTraces方法:

 private fun FindLeakInput.buildLeakTraces(pathFindingResults: PathFindingResults): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    // .....

    val deduplicatedPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
   //.....
    return applicationLeaks to libraryLeaks
  }

在通过findPathsFromGcRoots()获取的节点中,一个泄露对象可能会有多个引用路径,所以我们还需要做一次遍历,找到每个泄露对象的最短路径:

private fun deduplicateShortestPaths(inputPathResults: List<ReferencePathNode>): List<ReferencePathNode> {
    val rootTrieNode = ParentNode(0)

    for (pathNode in inputPathResults) {
    
      val path = mutableListOf<Long>()
      var leakNode: ReferencePathNode = pathNode
      while (leakNode is ChildNode) {
        path.add(0, leakNode.objectId)
        leakNode = leakNode.parent
      }
      path.add(0, leakNode.objectId)
      updateTrie(pathNode, path, 0, rootTrieNode)
    }

    val outputPathResults = mutableListOf<ReferencePathNode>()
    findResultsInTrie(rootTrieNode, outputPathResults)
    return outputPathResults
  }

在这里会构建一个最短路径的树。在这里会生成泄漏对象路径ReferencePathNode,然后最终会生成LeakTrace

然后会通过调用HeapAnalyzer.computeLeakStatuses()来计算路径上每个节点的泄露状态。

UI显示

在默认实现的DefaultOnHeapAnalyzedListener中,当前 hprof 文件分析成功后,会回调onHeapAnalyzed()方法:

override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
  // 入库                                         
  val id = LeaksDbHelper(application).writableDatabase.use { db ->                                
    HeapAnalysisTable.insert(db, heapAnalysis)                                   
  }                                                                                           
  val (contentTitle, screenToShow) = when (heapAnalysis) {                
    is HeapAnalysisFailure -> application.getString(
        R.string.leak_canary_analysis_failed 
    ) to HeapAnalysisFailureScreen(id) 
    is HeapAnalysisSuccess -> { 
      val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }                
      val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size     
      application.getString(                                                                      
          R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount  
      ) to HeapDumpScreen(id)                                                       
    }   
  }                                                                                               
  if (InternalLeakCanary.formFactor == TV) 
    showToast(heapAnalysis)  
    printIntentInfo()      
  } else {                                                                   
    // 显示通知栏消息                      
    showNotification(screenToShow, contentTitle)
  }                                                                                               
}                                                                                                 

点击通知栏的时候,唤起LeakActivity

val pendingIntent = LeakActivity.createPendingIntent(         
    application, arrayListOf(HeapDumpsScreen(), screenToShow) 
)                                                             

到这里就结束了,整个leakcanary的源码结构很清晰,值得学习,也值得反复学习。

参考:

官网

LeakCanary2源码

LeakCanary2 源码分析