LeakCanary实现原理

#### LeakCanary2.7实现原理分析 ####

`LeakCanary` 是Square公司开源的内存检测工具,与常用的AS自带的Profile工具,MAT工具相比使用非常简单(只需要一行代码即可),泄漏后生成的链式结构也很容易定位泄漏点。 

- 为什么初始化都不需要(2.x之前需要),一行代码就搞定?

- 其实现原理是什么?

- 其流程是什么? 

我们具体分析下。  

##### 1 LeakCanary初始化

[LeakCanary]: https://github.com/square/leakcanary

一行代码引入:

``` groovy
    implementation 'com.squareup.leakcanary:leakcanary-android:2.7'
```

在leakcanary-android这个lib依赖的leakcanary-android-core的AndroidManifest文件中注册了ContentProvider

``` groovy
    <application>
        <provider
            android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
            android:authorities="${applicationId}.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false" />
    </application>
```

熟悉应用启动流程的同学都知道,ContentProvider.onCreate方法要早于Application.onCreate

```java
    //ActivityThread.java的handleBindApplication方法

    //先安装ContentProvider
    if (!ArrayUtils.isEmpty(data.providers)) {
        installContentProviders(app, data.providers);
    }

    //Instrumentation仪表盘的onCreate
    mInstrumentation.onCreate(data.instrumentationArgs);
    try {
        //Instrumentation来具体调用我们应用生命周期,先执行我们Application.onCreate
        mInstrumentation.callApplicationOnCreate(app);
    } catch (Exception e) {
        if (!mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
                "Unable to create application " + app.getClass().getName()
                + ": " + e.toString(), e);
        }
    }

```

这样AppWatcherInstaller的onCreate就会先执行

```kotlin
//AppWatcherInstaller.onCreate  
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
```

继续看AppWatcher.manualInstall做了什么

```kotlin
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),   //retainedDelayMillis初始化是5s,后面会用到
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    this.retainedDelayMillis = retainedDelayMillis
    // 省略无关紧要代码
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }

  //LeakCanary中需要监控的对象
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
```

可以看到其初始化时通过`ActivityWatcher`,`FragmentAndViewModelWatcher`,`ServiceWatcher` 执行其install方法

以ActivityWatcher的install方法为例

```kotlin
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }
```

可以看到其主要监听了ActivityLifecycleCallbacks的onActivityDestroyed方法,也就是说在Activity准备销毁时LeakCanary开始监听其是否会泄漏。

##### 2 LeakCanary原理:WeakReference和ReferenceQueue的妙用

```java
public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
```

WeakRefernece(作为容器)的构造函数中可以传ReferenceQueue,表示被弱引用容器标识的对象如果被GC,此时会把WeakReference容器添加到指定的ReferenceQueue中,也就是说如果对象被回收,就可以从ReferenceQueue中poll出WeakReference容器,即:如果从ReferenceQueue中poll不出就代表可能泄露。看下面例子就能明白

```java
ReferenceQueue referenceQueue = new ReferenceQueue();
Object obj = new Object();

//把obj放入weakReference,并和一个referenceQueue关联
//当obj被gc回收后,盛放它的weakReference会被添加与之关联的referenceQueue
WeakReference weakReference = new WeakReference(obj,referenceQueue);
System.out.println("从queue里取出来的为 " + referenceQueue.poll()); //因为对象没被回收,所以此时poll出来的为空

//把obj置空,让它没有强引用
obj = null;
//gc,让可以回收的对象回收
Runtime.getRuntime().gc();
try{
    Thread.sleep(1000);
}catch (Exception e){}

//gc后拿到的reference和wekreference是同一个对象,说明对象被回收后WeakReference被添加到referenceQueue
Reference findRef = referenceQueue.poll();
System.out.println("findRef = " +findRef + "是否等于上面的weakReference = " + (findRef == weakReference));

```

基于以上原理,把需要观察的对象(Activity,fragment)等加到弱引用容器中,并指定ReferenceQueue,一段时间后从queue里判断是否能拿到弱引用。

##### 3 具体流程

继续分析源码,在onActivityDestroyed回调后执行了ObjectWatcher.expectWeaklyReachable()

```kotlin
  @Synchronized override fun expectWeaklyReachable(watchedObject: Any,description: String) {
    if (!isEnabled()) {
      return
    }
    //先把已经GC的清空下
    removeWeaklyReachableObjects()
    // key是随机生成的uuid
    val key = UUID.randomUUID().toString()
    val watchUptimeMillis = clock.uptimeMillis()
    //初始化弱引用,指定queue,并加入观察对象列表
    val reference =KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    watchedObjects[key] = reference
    //异步任务处理
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

  //从ReferenceQueue中poll出来弱引用,如果能拿到,说明被弱引用引用的对象已经被GC,就可以从观察对象列表中删除
  private fun removeWeaklyReachableObjects() {
    // 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.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
```

而异步任务checkRetainedExecutor初始化中用到的retainedDelayMillis在LeakCanary初始化方法manualInstall中已经设置5s

```kotlin
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )
```

也就是说5s之后调用了moveToRetained把key放到了怀疑列表。

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

继续看InternalLeakCanary.onObjectRetained()

```kotlin
  override fun onObjectRetained() = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
```

HeapDumpTrigger.scheduleRetainedObjectCheck()

```kotlin
  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
  }
```

最主要HeapDumpTrigger.checkRetainedObjects(),只分析一些关键代码

```kotlin
 private fun checkRetainedObjects() {
    var retainedReferenceCount = objectWatcher.retainedObjectCount
     //如果怀疑列表数量大于0,执行一次GC
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    //config.retainedVisibleThreshold配置的5,也就是说这方法会把目前怀疑列表的count和5对比
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }
```

这里也就是说会判断怀疑列表的数量,如果大于5就会执行dumpHeap()来具体分析下是否有泄漏

```kotlin
  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
      //这里执行heapDumper.dumpHeap方法生成hprof文件
      when (val heapDumpResult = heapDumper.dumpHeap()) {///省略
      }
      //生成文件后HeapAnalyzerService这个服务的runAnalysis方法开始分析hprof文件
      HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
      )
    }
  }
```

内存分析服务HeapAnalyzerService.runAnalysis()方法,开启了HeapAnalyzerService服务,其继承于IntentService

```kotlin
 fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
      heapDumpDurationMillis?.let {
        intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
      }
      startForegroundService(context, intent)
    }
```

其重写的异步方法onHandleIntentInForeground

```kotlin
  override fun onHandleIntentInForeground(intent: Intent?) {
    // 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
    //如果dumpfile文件存在,就分析此文件
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } 
  }
```

这里执行了heapAnalyzer.analyze来具体分析

```kotlin
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) { }
  return heapAnalyzer.analyze(
    heapDumpFile = heapDumpFile,
    leakingObjectFinder = config.leakingObjectFinder,
    referenceMatchers = config.referenceMatchers,
    computeRetainedHeapSize = config.computeRetainedHeapSize,
    objectInspectors = config.objectInspectors,
    metadataExtractor = config.metadataExtractor,
    proguardMapping = proguardMappingReader?.readProguardMapping()
  )
}
```

这里注意到HeapAnalyzer已经不是用的haha算法了,2.7版本已经用的是shark包了

```kotlin
fun analyze(
    heapDumpFile: File,
    leakingObjectFinder: LeakingObjectFinder,
    referenceMatchers: List<ReferenceMatcher> = emptyList(),
    computeRetainedHeapSize: Boolean = false,
    objectInspectors: List<ObjectInspector> = emptyList(),
    metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
    proguardMapping: ProguardMapping? = null
  ): HeapAnalysis {
     return try {
      listener.onAnalysisProgress(PARSING_HEAP_DUMP)
      val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
      sourceProvider.openHeapGraph(proguardMapping).use { graph ->
        val helpers =
          FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
        val result = helpers.analyzeGraph(
          metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
        )
        val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
      }
    }
  }
```

这个方法稍微有点复杂,大概意思是从内存dump文件中基于根对象可达性算法(helpers.analyzeGraph)分析对象是否存在泄漏,并返回HeapAnalysisSuccess包装后的对象。继续看下这个可达性算法

```kotlin
  private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
  ): HeapAnalysisSuccess {
    //省略解析文件的操作
    //findLeakingObjectIds 这个方法通过特定规则过滤之后得到一组泄露对象id的set集合
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    //解析完的泄露对象调用findLeaks来分析最短路径链
    val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
    return HeapAnalysisSuccess(
      heapDumpFile = heapDumpFile,
      createdAtTimeMillis = System.currentTimeMillis(),
      analysisDurationMillis = since(analysisStartNanoTime),
      metadata = metadataWithCount,
      applicationLeaks = applicationLeaks,
      libraryLeaks = libraryLeaks,
      unreachableObjects = unreachableObjects
    )
  }
  // 根据解析到的GCRoot对象和泄露的对象,在graph中搜索最短引用链
  private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
    val pathFinder = PathFinder(graph, listener, referenceMatchers)
    //这里采用的是广度优先遍历的算法进行搜索出多条路径
    val pathFindingResults =
      pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
    //查找不可达对象
    val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)

    val shortestPaths =
      deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)

    val inspectedObjectsByPath = inspectObjects(shortestPaths)

    val retainedSizes =
      if (pathFindingResults.dominatorTree != null) {
        computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree)
      } else {
        null
      }
    //findPathsFromGcRoots基于广度优先算法找到的可能多条路径,这里又基于深度优先算法找到最短路径
    val (applicationLeaks, libraryLeaks) = buildLeakTraces(
      shortestPaths, inspectedObjectsByPath, retainedSizes
    )
    return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects)
  }
```

查找不可达对象

```kotlin
  private fun FindLeakInput.findUnreachableObjects(
    pathFindingResults: PathFindingResults,
    leakingObjectIds: Set<Long>
  ): List<LeakTraceObject> {
    val reachableLeakingObjectIds =
      pathFindingResults.pathsToLeakingObjects.map { it.objectId }.toSet()

    val unreachableLeakingObjectIds = leakingObjectIds - reachableLeakingObjectIds

    val unreachableObjectReporters = unreachableLeakingObjectIds.map { objectId ->
      ObjectReporter(heapObject = graph.findObjectById(objectId))
    }

    objectInspectors.forEach { inspector ->
      unreachableObjectReporters.forEach { reporter ->
        inspector.inspect(reporter)
      }
    }

    val unreachableInspectedObjects = unreachableObjectReporters.map { reporter ->
      val reason = resolveStatus(reporter, leakingWins = true).let { (status, reason) ->
        when (status) {
          LEAKING -> reason
          UNKNOWN -> "This is a leaking object"
          NOT_LEAKING -> "This is a leaking object. Conflicts with $reason"
        }
      }
      InspectedObject(
        reporter.heapObject, LEAKING, reason, reporter.labels
      )
    }

    return buildLeakTraceObjects(unreachableInspectedObjects, null)
  }
```

具体Shark分析hprof流程参照https://linjiang.tech/2019/12/25/leakcanary/,https://juejin.cn/post/6877829309961076750到此整个LeakCanary流程分析完。

##### 4 hprof文件解析

hprof的具体协议链接:http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#mozTocId848088

hprof文件的标准协议主要由head和body组成,body是由一系列不同类型的Record组成,Record主要用于描述trace、object、thread等信息,依次分为4个部分:TAG、TIME、LENGTH、BODY,其中TAG就是表示Record类型。Record之间依次排列或嵌套,最终组成hprof文件。

多个Record被进抽象为HprofMemoryIndex,Index可以快速定位到对应对象在hprof文件中的位置;最终Index和Hprof一起再组成HprofGraph,graph做为hprof的最上层描述,将所有堆中数据抽象为了 gcRoots、objects、classes、instances等集合。

后续通过 `Graph` 的 `objects` 集合找出泄露对象,然后通过<font color="red">广度优先遍历</font>找出其到 `GcRoot` 的引用路径链,结束流程。

##### 5 总结

LeakCanary怎么实现检测内存泄露的?

1 利用ContentProvider的onCreate方法早于Application.oncreate,在其方法里自动初始化,所以2.X版本只需要导入包即可。并监听activity的onActivityDestroyed等的销毁。

2 在监听对象的销毁方法中,先把对象加入到观察者对象列表中,利用WeakReference和ReferenceQueue的特性,5s后判断ReferenceQueue中是否能poll出相应的WeakReference来判断对象是否泄露,能poll出说明被销毁,从观察者列表中移除此对象,不能poll出说明还没被销毁,把其加入到怀疑列表。

3 如果怀疑列表的count大于默认的5,会执行一次HeapDumper.dumpHeap方法来dump一次内存快照,生成hprof文件后调用HeapAnalyzerService这个后台服务的runAnalysis方法开始分析此文件。

4 2.X版本分析内存文件用的是shark包下的heapAnalyzer.analyze,而1.X版本用的是haha包下的。原理都是基于hprof文件找到最短的引用链(先基于广度优先找到所有路径,再基于深度优先找到最短路径),最终构建为applicationLeaks和LibraryLeaks两种形式的泄露描述。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值