LeakCanary 2.0源码分析与总结

本文基于LeakCanary 2.0源码分析
LeakCanary - 官方地址
LeakCanary - GitHub代码地址

LeakCanary 是什么

概念:LeakCanary是针对Android应用的一个内存泄漏监控三方库
能力:Activity、Fragment以及自主监控的任何对象
出品:Square

LeakCanary 作用

基于对Android Framework层的认知,LeakCanary提供更精准的泄漏原因分析能力,从而帮助开发者快速减少OOM Crash问题

LeakCanary 工作原理

预备知识

什么是内存泄漏

在Java运行环境下,内存泄漏是指某个程序错误导致应用长时间一直保留某个不在需要的对象,以至于它不能被回收,而它是会占用内存的,这就意味着内存泄漏了。持续累加,最终有可能导致发生内存溢出问题。
例如一个Activity执行完onDestroy方法后,它仍然被一个static变量强引用,从而阻止了Activity被GC回收,导致Activity发生内存泄漏

怎么判断一个对象是否泄漏

从GC Roots出发进行遍历,强引用可到达对象,都是存活对象,不可达对象则为即将被回收的对象。如果那些存活对象本应该是要被回收的,那么这个对象就是发生了内存泄漏(见下图,引用一张图说明)
实际过程通常作法是针对核心对象Activity、Fragment进行监控分析

如何开始监控

LeakCanary通过实现四大组件中的ContentProvider,所以可以在App启动的时候执行到LeakCanary的AppWatcherInstaller.onCreate方法,从而完成0侵入实现注册监控流程

监控什么对象以及如何监控到目标对象

Activity: 通过注册 Application.ActivityLifecycleCallbacks实现onActivityDestroyed执行回调,以监控需要被回收的Activity是否被回收
Fragment: 通过注册 FragmentManager.FragmentLifecycleCallbacks实现onFragmentViewDestroyed和onFragmentDestroyed回调,以监控需要被回收的Fragment或者View是否被回收

如何确认目标对象泄漏

  1. 执行回收:目标对象如果在一个缓冲时间(5s)仍未被回收,我们通过手动执行GC,然后在确认其是否真的不能被回收
  2. 确认是否回收:JVM中,如果创建一个含有ReferenceQueue的WeakReference的A对象,这个WeakReference对应的A对象如果被回收了,则A会被自动加入到ReferenceQueue,所以我们可以通过维护一个ReferenceQueue,通过创建目标对象的含有ReferenceQueue的WeakReference,从而监听到目标对象是否被回收

更多参考:Reference和ReferenceQueue深入解读

如何分析目标对象泄漏的原因

泄漏原因即寻找泄漏路径

  1. 通过Debug.dumpHprofData,dump一份hprof数据
  2. 读取hprof数据,整理出一份GcRoots对象索引
  3. 排序GcRoots对象,并构造一份ReferencePathNode树
  4. BFS遍历,找到泄漏对象的最短路径节点
  5. 根据节点,生成泄漏路径

如何呈现目标对象泄漏的原因

  1. 生成一个HeapDumpScreen,呈现泄漏信息
  2. 发出通知

LeakCanary 2.0与1.x版本对比

内容 2.0 1.0
语言 kotlin java
使用 仅需引入库,自动注册监控 除引入库,还需要手动执行install
内存分析 shark,基于Okio的自实现的轻巧内存分析库 haha三方库
其它 fragment,支持 androidx

LeakCanary 源码分析

主要包的结构介绍

主要工作的时序图

相关源码

注册流程

AppWatcherInstaller.onCreate

// 继承 ContentProvider
internal sealed class AppWatcherInstaller : ContentProvider() {
    // App启动 执行 onCreate 
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // 注册启动(0 侵入)
    InternalAppWatcher.install(application)
    return true
  }

}

InternalAppWatcher.install

fun install(application: Application) {
    InternalAppWatcher.application = application
    
    val configProvider = { AppWatcher.config }
    // Activity destroy方法监控注册
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    // Fragment destroy方法监控注册
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
}

ActivityDestroyWatcher.install

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {
    // 构造 Application.ActivityLifecycleCallbacks
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
          objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
        }
      }
    }

  companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      // 注册 生命周期监听回调
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

InternalLeakCanary.invoke

  // 初始化相关类
override fun invoke(application: Application) {
    this.application = application
  
    // 添加保留对象监听
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
    // Android heap dumper类
    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
    // GC触发器
    val gcTrigger = GcTrigger.Default
    
    val configProvider = { LeakCanary.config }
    // 相关线程 handler
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)
    // Heap Dump 触发器
    heapDumpTrigger = HeapDumpTrigger(
       application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
       configProvider
    )
    ...
}
监控流程

以Activity.onDestory为例
ActivityDestroyWatcher.lifecycleCallbacks

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
            // 开始监控 销毁的activity
          objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
        }
      }
    }```

ObjectWatcher.watch

  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    // 移除已经回收的监听对象
    removeWeaklyReachableObjects()
    // 随机key
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 构造KeyedWeakReference 用来监听目标对象
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    // 存储 key + reference
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
        // 执行 没有回收流程
      moveToRetained(key)
    }
  }

ObjectWatcher.moveToRetained

  @Synchronized private fun moveToRetained(key: String) {
    // 再次移除被回收的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    // 如果没有被回收 则开始执行对象未被回收流程
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

HeapDumpTrigger

  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }
  
  fun onObjectRetained() {
    scheduleRetainedObjectCheck("found new object retained")
  }

  private fun scheduleRetainedObjectCheck(reason: String) {
    checkScheduled = true
    backgroundHandler.post {
      checkScheduled = false
      checkRetainedObjects(reason)
    }
  }

private fun checkRetainedObjects(reason: String) {
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    if (!config.dumpHeap) {
      SharkLog.d { "No checking for retained object: LeakCanary.Config.dumpHeap is false" }
      return
    }
    SharkLog.d { "Checking retained object because $reason" }

    var retainedReferenceCount = objectWatcher.retainedObjectCount
    // 如果还有未被回收的目标对象,则出发GC,
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    // 触发GC后,对象仍未被回收,开始dump
    dumpHeap(retainedReferenceCount, retry = true)
  }

HeapDumpTrigger.dumpHeap

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    // 储存Android 的资源id 及其对应的name 
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // dumpHeap 到file
    val heapDumpFile = heapDumper.dumpHeap()

    lastDisplayedRetainedObjectCount = 0
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    // 启动service 开始dump分析
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }
  

AndroidHeapDumper.dumpHeap

 override fun dumpHeap(): File? {
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
    ...
    // 调用Debug的dumpHprofData 到目标文件
    return Debug.dumpHprofData(heapDumpFile.absolutePath)
             heapDumpFile
  }

分析流程

HeapAnalyzerService.onHandleIntentInForeground

  override fun onHandleIntentInForeground(intent: Intent?) {
    // 获取目标 heap dump file
     val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    // 构造 heap分析器
    val heapAnalyzer = HeapAnalyzer(this)
    // ...
    // 执行分析流程
    val heapAnalysis =
      heapAnalyzer.analyze(
          heapDumpFile,
          config.referenceMatchers,
          config.computeRetainedHeapSize,
          config.objectInspectors,
          if (config.useExperimentalLeakFinders) config.objectInspectors else listOf(
              ObjectInspectors.KEYED_WEAK_REFERENCE
          ),
          config.metatadaExtractor,
          proguardMappingReader?.readProguardMapping()
      )

    // 回调分析完成
    config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
  }

HeapAnalyzer.analyze

fun analyze(
    heapDumpFile: File,
    referenceMatchers: List<ReferenceMatcher> = emptyList(),
    computeRetainedHeapSize: Boolean = false,
    objectInspectors: List<ObjectInspector> = emptyList(),
    leakFinders: List<ObjectInspector> = objectInspectors,
    metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
    proguardMapping: ProguardMapping? = null
  ): HeapAnalysis {
    val analysisStartNanoTime = System.nanoTime()

    try {
      listener.onAnalysisProgress(PARSING_HEAP_DUMP)
      // 读取 文件,然后执行分析
      Hprof.open(heapDumpFile)
          .use { hprof ->
            // Hprof -> graph 转换过程 目标获取GcRoots index
            val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)

            listener.onAnalysisProgress(EXTRACTING_METADATA)
            // 获取Android相关 metadata (如sdk版本、收集厂商等信息)
            val metadata = metadataExtractor.extractMetadata(graph)
            
            val findLeakInput = FindLeakInput(
                graph, leakFinders, referenceMatchers, computeRetainedHeapSize, objectInspectors
            )
            // 找泄漏最短路径
            val (applicationLeaks, libraryLeaks) = findLeakInput.findLeaks()
            listener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
            // 返回分析成功结果
            return HeapAnalysisSuccess(
                heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime), metadata,
                applicationLeaks, libraryLeaks
            )
          }

    }

HeapAnalyzer.findLeaks

  private fun FindLeakInput.findLeaks(): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    // 找未被回收对象的 objectId
    val leakingInstanceObjectIds = findRetainedObjects()
    // 构造pathFinder对象
    val pathFinder = PathFinder(graph, listener, referenceMatchers)
    val pathFindingResults =
    // ⚠️ 找泄漏对象到GcRoots的最短路径
    pathFinder.findPathsFromGcRoots(leakingInstanceObjectIds, computeRetainedHeapSize)

   // 返回 泄漏路径
    return buildLeakTraces(pathFindingResults)
  }

PathFinder.findPatchsFromGcRoots

  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)
    // 执行 state。findPathsFromGcRoots
    return state.findPathsFromGcRoots()
  }
  
  private fun State.findPathsFromGcRoots(): PathFindingResults {
    // GcRoots 生成节点队列树
    enqueueGcRoots()

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

        // 泄漏的节点,则添加到shortestPathsToLeakingObjects 直到,全部找完 
      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
          }
        }
      }
    }
    return PathFindingResults(shortestPathsToLeakingObjects, dominatedObjectIds)
  }
呈现流程

DefaultOnHeapAnalyzedListener.onHeapAnalyzed

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    // 写入 db
    val (id, groupProjections) = LeaksDbHelper(application)
        .writableDatabase.use { db ->
      val id = HeapAnalysisTable.insert(db, heapAnalysis)
      id to LeakTable.retrieveHeapDumpLeaks(db, id)
    }

    // 生成 泄漏信息到屏幕展示
    val (contentTitle, screenToShow) = when (heapAnalysis) {
      is HeapAnalysisFailure -> application.getString(
          R.string.leak_canary_analysis_failed
      ) to HeapAnalysisFailureScreen(id)
      is HeapAnalysisSuccess -> {
        var leakCount = 0
        var newLeakCount = 0
        var knownLeakCount = 0
        var libraryLeakCount = 0

        for ((_, projection) in groupProjections) {
          leakCount += projection.leakCount
          when {
            projection.isLibraryLeak -> libraryLeakCount += projection.leakCount
            projection.isNew -> newLeakCount += projection.leakCount
            else -> knownLeakCount += projection.leakCount
          }
        }

        application.getString(
            R.string.leak_canary_analysis_success_notification, leakCount, newLeakCount,
            knownLeakCount, libraryLeakCount
        ) to HeapDumpScreen(id)
      }
    }

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

    val contentText = application.getString(R.string.leak_canary_notification_message)
    // 构建通知
    Notifications.showNotification(
        application, contentTitle, contentText, pendingIntent,
        R.id.leak_canary_notification_analysis_result,
        LEAKCANARY_MAX
    )
  }


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容