首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >内存泄露分析工具:LeakCanary 原理分析

内存泄露分析工具:LeakCanary 原理分析

作者头像
胡飞洋
发布于 2022-02-25 02:32:32
发布于 2022-02-25 02:32:32
5.9K01
代码可运行
举报
运行总次数:1
代码可运行

内存泄露Android性能优化的重要内容,而Leakcanary是发现和分析内存问题的常用工具。本篇文章就对LeakCanary的原理进行分析,希望能帮助到你。 作者:FreemanGordon 原文: https://juejin.cn/user/3368559355374285 本文由作者授权发布


一、什么是内存泄漏

内存泄漏指的是程序在向系统申请分配内存空间,使用完毕后未释放,结果导致一直占据该 内存单元,程序无法再使用该内存单元。在Android系统中,一般指的是对象在超出自身生命周期后, 该对象仍然没有被回收。泄漏包括的种类有:

  • java 堆泄漏
  • native 野指针
  • fd 句柄泄漏

泄漏容易导致应用进程内存飙升,最终导致OOM或者Too many open files相关的崩溃。这些崩溃点 一般都是压倒骆驼的最后一根稻草,并非崩溃的根本原因,需要dump内存或者句柄的打开情况,才能比较直观的修复问题

二、检测内存泄漏的方案

1.字节Liko

在 OOM 和内存触顶时通过用户无感知 dump 来获取 HPROF 文件,当 App 退出到后台且内存 充足的情况进行分析,裁剪 HPROF 回传进行分析,线上MAT分析HPROF文件并生成链路和报表。 可以上报大对象或者频繁创建的过多的小对象导致的内存吃紧,预防OOM

2.快手KOOM

利用系统内核COW(Copy-on-write,写时复制)机制,每次dump内存镜像前先暂停虚拟机, 然后fork子进程来执行dump操作,父进程在fork成功后立刻恢复虚拟机运行,整个过程对于父进程来讲 总耗时只有几毫秒。内存镜像于闲时进行独立进程单线程本地分析,分析完即删除。

https://github.com/KwaiAppTeam/KOOM

3.Leakcanary定制

利用对 leakcanary 的定制,并将泄漏 trace 上报到业务 server 中

三、Leakcanary 概览

LeakCanary: It automatically watches destroyed activities and fragments, triggers a heap dump, runs Shark Android and then displays the result.

LeakCanary 的图标为一只鸟,实际上就是 Canary 的直译 —— 金丝雀。因为在较早前,由于金丝雀对有害气体的敏感性,在矿场里面经常用来探测矿井的气体 LeakCanary 分为五部分

  • 应用层面对泄漏的监听、内存dump和结果展示
  • Shark,一个基于kotlin实现的堆分析库,类似于 MAT 工具
  • 内存分析Service
  • 泄漏展示UI
  • 泄漏数据库

四、Leakcanary 引入

新版本的Leakcanary引入很简单,只需一个gradle依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

或者

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.7'

两者相同点:

  • 都使用AppWatcherInstaller$MainProcess(因为要dump主进程的内存),利用ContentProvider在进程启动时自动初始化
  • 对象检测和内存 dump 都在主进程
  • 可以通过配置xml的leak_canary_watcher_auto_install属性,进行定制化初始化时机

两者的区别:

  • leakcanary-android 内存分析服务 HeapAnalyzerService 运行在主进程子线程;
  • leakcanary-android-process 内存分析服务 HeapAnalyzerService 运行在独立进程 :leakcanary 中

五、Leakcanary启动时机

早期版本需要在应用Application的onCreate中处理Leakcanary的初始化,新版本中为了降低接入成本, 将Leakcanary的初始化收拢到库定义的AppWatcherInstaller里面,原理是利用ContentProvider的 onCreate的初始化时机早于Application的onCreate(PS:晚于Application的attachBaseContext)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
AcitivtyThread.java

private void handleBindApplication(AppBindData data) {
    .....省略代码
    try {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        app = data.info.makeApplication(data.restrictedBackupMode, null); // Application#attachBaseContext()
        .....省略代码
        // don't bring up providers in restricted mode; they may depend on the
        // app's custom Application class
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                installContentProviders(app, data.providers); // 创建ContentProvider
            }
        }

        .....省略代码
        try {
            mInstrumentation.callApplicationOnCreate(app);// Application#onCreate()
        }
        .....省略代码
}

六、Leakcanary初始化

AppWatcherInstaller.onCreate调用Leakcanary的初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /**
   * contentProvider 创建时开始初始化Leakcanary
   */
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application) // 初始化
    return true
  }

AppWatcher.manualInstall 会设置一个默认的对象进行检测的延时时间和默认检测的对象类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 添加进watchedObjects后5s开启检测
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) // 检测对象
  ) {
    // ...省略代码

    // leakcanary 核心组件,负责实现泄漏的检测和触发 heap dump。默认实现类是:InternalLeakCanary.kt
    LeakCanaryDelegate.loadLeakCanary(application)

    // 注册默认检测的对象
    watchersToInstall.forEach {
      it.install()
    }
  }

默认检测的对象有

  • Activity
  • Fragment(view和Fragment本身)
  • ViewMoel
  • RootView(Window)
  • Service
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher), // Activity
      FragmentAndViewModelWatcher(application, reachabilityWatcher), // Fragment 和 viewModel
      RootViewWatcher(reachabilityWatcher), // window
      ServiceWatcher(reachabilityWatcher) // service
    )
  }

每种类型都会在特定的时机加入到 watchedObjects(一个map,保存需要检测的对象的弱引用,定义在 ObjectWatcher.kt) 里面等待检测,主要通过一些组件声明周期监听回调和一些 hook 点实现

6.1 Actiity 检测时机

通过注册 ActivityLifecycleCallbacks 回调,在 onActivityDestroyed 中将该 activity 加入 watchedObjects 里面等待检测

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ActivityWatcher.kt

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

6.2 Fragment(AndroidX) 检测时机

通过注册 FragmentLifecycleCallbacks 回调,在 onFragmentViewDestroyed 和 onFragmentDestroyed 分别将 View 和 Fragment 加入 watchedObjects 里面等待检测

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
AndroidXFragmentDestroyWatcher.kt

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null) {
        reachabilityWatcher.expectWeaklyReachable(
          view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
          "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

6.3 ViewModel 检测时机

ViewModel 的检测比较巧妙,Leakcanary 在 Fragment onCreate 时为当前的 Fragment 添加一个 ViewModel,而这个 ViewModel 利用跟随宿主生命周期的原理,在被执行 onClear 时,通过 hook 当前宿主的所有 viewmodel 后,遍历将这些 viewmodel 加入到 watchedObjects 里面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
AndroidXFragmentDestroyWatcher.kt

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      // 在 Fragment 执行 onCreate 时为当前 Fragment 添加一个 ViewModel
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }



ViewModelClearedWatcher.kt
  fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java) // 添加到 storeOwner 中
    }

    // hook 到当前 Fragment 的所有 viewModel
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      @Suppress("UNCHECKED_CAST")
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }

  // 该 ViewModel 被执行 onCleared 时,将所有的 viewModel 加入到检测队列中
  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

6.3 Service 检测时机

Service 类似于 Activity,也是在 onDestroy 时将 Service 对象加入到 watchedObjects 里面,但是由于 Service 没有开放声明周期的回调,所以也是通过 hook 的放获取 Service 的声明周期

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ServiceWatcher.kt

  override fun install() {
    checkMainThread()
    check(uninstallActivityThreadHandlerCallback == null) {
      "ServiceWatcher already installed"
    }
    check(uninstallActivityManager == null) {
      "ServiceWatcher already installed"
    }
    try {
      // hook ActivityThread 里面的 mH 的 mCallback
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        // 代理对象
        Handler.Callback { msg ->
          // https://github.com/square/leakcanary/issues/2114
          // On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
          // instead of an IBinder. This crashes on a ClassCastException. Adding a type check
          // here to prevent the crash.
          if (msg.obj !is IBinder) {
            return@Callback false
          }

          // 拦截 STOP_SERVICE 消息,这里主要是预处理获取到即将要被 destroy 的 service 对象
          if (msg.what == STOP_SERVICE) {
            val key = msg.obj as IBinder
            activityThreadServices[key]?.let {
              onServicePreDestroy(key, it)
            }
          }
          // 执行原有逻辑
          mCallback?.handleMessage(msg) ?: false
        }
      }

      // hook Activity Manage 对象
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        // 动态代理对象
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          // hook 到 service 真正 destroy 的时机,这里没法获取到servcie对象,所以要前面的预操作:onServicePreDestroy
          if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
            val token = args!![0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              // 将service封装成弱引用,5s 后触发留存检测
              onServiceDestroyed(token)
            }
          }
          // 执行原有逻辑
          try {
            if (args == null) {
              method.invoke(activityManagerInstance)
            } else {
              method.invoke(activityManagerInstance, *args)
            }
          } catch (invocationException: InvocationTargetException) {
            throw invocationException.targetException
          }
        }
      }
    } catch (ignored: Throwable) {
      SharkLog.d(ignored) { "Could not watch destroyed services" }
    }
  }

再看 onServiceDestroyed 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private fun onServiceDestroyed(token: IBinder) {
    // 通过 token 匹配到预处理时获取到的 service 对象
    servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
      serviceWeakReference.get()?.let { service ->
      // 将 service 对象加入到 watchedObjects 里面
        reachabilityWatcher.expectWeaklyReachable(
          service, "${service::class.java.name} received Service#onDestroy() callback"
        )
      }
    }
  }

上面已经把走读所有默认检测的对象的时机,在这之后,该对象将会被封装成一个弱引用,并关联一个回收队列。原理就是:

  • 弱引用包含的对象在无其他引用的情况下,执行 GC,该对象将会被加入到回收队列

举个例子,一个 Activity A 被封装成一个弱引用 weakA,并把这个弱引用 weakA 加入到 watchedObjects 中,在 5s 后触发 gc,如果 A 的引用被加入到回收队列中,则该 A 是可已被回收,那将 weakA 从 watchedObjects 中移除。反之,如果 A 的引用没有被加入到回收队列中,则该 A 还有被其他对象引用,则判定为内存泄露,触发 heap dump 和 analyze 流程

七、Leakcanary heap dump 和 analyze 流程

在前面的文章中已经介绍了目标对象在特定的时机会加入到 watchedObjects 中,在被判断泄露后,就会启动 heap dump,这个流程比较复杂,我们以 Activity 泄漏举例,先看张 UML 图

再回头看看 Activity 对象的监听时机

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ActivityWatcher.kt

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

Tips: 在上面代码中的 noOpDelegate 通过 Kotlin 的委托机制和 java 的动态代理,只需要实现关注的的接口的方法,其他方法由委托自动补齐,这样代码可以清爽一点

reachabilityWatcher 其实是 ObjectWatcher,接着看 expectWeaklyReachable 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 清理一遍已经被回收的对象
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 封装成一个弱引用,若无其他引用,则该弱引用里面的对象在 gc 时将被回收并且会加入到 queue 里面
    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"
    }

    // 添加进去一个留存监测map,如果gc后,仍然在该map的对象则会判定为泄露
    watchedObjects[key] = reference
    // 添加一个监测 runnable, 5s 后执行
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

先看看 removeWeaklyReachableObjects 的实现,这个方法在很多地方都有调用,是为了能够及时清除已经被回收的对象记录

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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 中移除记录
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

在清理完成后,Leakcanary 将 Activity 对象封装成一个弱引用,并关联一个 UUID 生成的 key 和一个回收队列。并将这个key 和 弱引用对象 key-value 的形式加入到 watchedObjects,随机 checkRetainedExecutor post 一个 runnable 到主线程,执行时间默认为初始化时设置的延迟 5s 钟。再看下 moveToRetained(key) 的实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // onObjectRetainedListeners 实际就是在初始化时传入的 InternalLeakCanary.kt 对象
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

首先回到初始化时 InternalLeakCanary 处理的初始化逻辑

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
InternalLeakCanary.kt

  override fun invoke(application: Application) {
    // 传入 application 对象
    _application = application

    checkRunningInDebuggableBuild()

    // 往 ObjectWatcher 注册对象留存检测监听
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    // heap dump
    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    // 判断是否符合 GC 条件
    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    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
    )
    // 应用前后台监听,前后台监听逻辑差异化处理
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)

    // 在桌面添加 leakcanary 图标
    addDynamicShortcut(application)

    // We post so that the log happens after Application.onCreate()
    mainHandler.post {
      // https://github.com/square/leakcanary/issues/1981
      // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
      // which blocks until loaded and that creates a StrictMode violation.
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
  }

再回到前面的 onObjectRetained 方法,最终会调用到 HeapDumpTrigger.kt 的 scheduleRetainedObjectCheck()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HeapDumpTrigger.kt

  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) { // 大于 0 表示已经在监测,避免频繁重复检测
      return
    }
    // 记录当前检测的时间
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({ // 子线程
      checkScheduledAt = 0
      // 检测留存的对象
      checkRetainedObjects()
    }, delayMillis)
  }

通过设置时间戳避免了频繁重复检测,并 post 了一个 runnable 到子线程中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HeapDumpTrigger.kt

  private fun checkRetainedObjects() {
    // 是否可以触发dump heap
    val iCanHasHeap = HeapDumpControl.iCanHasHeap()

    val config = configProvider()

    if (iCanHasHeap is Nope) {
      if (iCanHasHeap is NotifyingNope) { // 发送一个 notification, 用户点击后,可以强制触发监测
        // ...省略代码
      }
      return
    }

    // 获取当前仍然留存的对象个数
    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc() // 触发GC
      retainedReferenceCount = objectWatcher.retainedObjectCount // 再次获取没有被回收的对象个数
    }

    /**
     *  根据留存个数,判断是否需要启动 dump heap
     *  为了尽量减少影响,应用在前后台判断的个数并不一样,前台默认至少5个,后台则为至少1个(切后台的时间要超过监测周期)
     */
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { // 一分钟之内不重复 heap dump
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
        objectCount = retainedReferenceCount,
        contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    dumpHeap( // 触发dump heap
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }

根据触发 GC 后查看 watchedObjects 中的个数,如果是在前台,则默认是大于等于5个,如果是在后台,则是大于等于 1 个,满足条件后,则触发 heap dump

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HeapDumpTrigger.kt

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // dump heap 结果
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> { // 失败
        // ...省略代码
      }
      is HeapDump -> { // 成功
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
         // 启动一个 IntentService 在子线程中分析 hprof
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
        )
      }
    }
  }

最终会把 dump 到的 hprof 文件交给 HeapAnalyzerService 去分析,而 HeapAnalyzerService 则启动 Shark 分析内存文件。Shark 尚未熟悉,就不在往下分析了。分析过程中的每个步骤都会有相应回调:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OnAnalysisProgressListener.kt

  enum class Step {
    PARSING_HEAP_DUMP,
    EXTRACTING_METADATA,
    FINDING_RETAINED_OBJECTS,
    FINDING_PATHS_TO_RETAINED_OBJECTS,
    FINDING_DOMINATORS,
    INSPECTING_OBJECTS,
    COMPUTING_NATIVE_RETAINED_SIZE,
    COMPUTING_RETAINED_SIZE,
    BUILDING_LEAK_TRACES, // 构建泄露路径
    REPORTING_HEAP_ANALYSIS
  }

值得一提的是, Leakcanary 开放了内存分析结果的回调,用户可以实现接口,拿到结果后上报到自己的业务服务器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HeapAnalyzerService.kt#onHandleIntentInForeground()
    /**
     * 可以自定义onHeapAnalyzedListener,获取到 HeapAnalysis 上报trace
     * LeakTraceWrapper.wrap(heapAnalysis.toString(), 120) 格式化trace
     */
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)

官方推荐的方法是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class LeakUploader : OnHeapAnalyzedListener {

  // leakcanary 的默认实现,主要是记录以后泄漏,避免重复上报
  val defaultListener = DefaultOnHeapAnalyzedListener.create()

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    TODO("Upload heap analysis to server")

    // Delegate to default behavior (notification and saving result) 
    defaultListener.onHeapAnalyzed(heapAnalysis)
  }
}

class DebugExampleApplication : ExampleApplication() {

  override fun onCreate() {
    super.onCreate()
    // 使用自定义的 OnHeapAnalyzedListener,处理完自己业务后再代理原有逻辑
    LeakCanary.config = LeakCanary.config.copy(
        onHeapAnalyzedListener = LeakUploader()
    )
  }
}

最后附上 Leakcanary 关于如何修复泄漏的官方文档,里面说明了如何查看泄漏源。 https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/

最后的最后摘录 leakcanary 关于使用弱引用修复内存泄露的说明,谨慎使用弱引用,从泄漏源头解决问题

Memory leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 胡飞洋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LeakCanary源码分析(基于2.4版本)
LeakCanary内部用到了Refercence及ReferenceQueue来实现对对象是否被回收的监听。这是LeakCanary的核心逻辑,因此在讲解LeakCanary之前,我们先来简单了解一下Refercence及ReferenceQueue。
李林LiLin
2021/03/04
1.9K0
LeakCanary2.6抽丝剥茧-源码分析
作为一个小Android,之前分析项过目中LeakCanary1.6.3的源码,今天在好奇心的驱使下,刷新了下maven发现,LeakCanary已经更新到2.6版本,今天对2.6的版本也进行源码的解析。
包子388321
2021/04/02
1.1K0
为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!
LeakCanary 是我们非常熟悉内存泄漏检测工具,它能够帮助开发者非常高效便捷地检测 Android 中常见的内存泄漏。在各大厂自研的内存泄漏检测框架(如腾讯 Matrix 和快手 Koom)的帮助文档中,也会引述 LeakCanary 原理分析。
用户9995743
2022/09/26
1.3K0
为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!
LeakCanary万字源码解析,干货满满
LeakCanary是Android面试中备受瞩目的一环,各大厂商如腾讯Matrix和快手Koom都自研内存泄漏检测框架,其原理分析也常被引述于帮助文档中。本文旨在抛却浮躁情绪,深入探究该框架的思想。
Coder昊白
2023/11/22
7520
LeakCanary万字源码解析,干货满满
Android内存泄露检测 LeakCanary2.0(Kotlin版)的实现原理
LeakCanary是一款非常常见的内存泄漏检测工具。经过一系列的变更升级,LeakCanary来到了2.0版本。2.0版本实现内存监控的基本原理和以往版本差异不大,比较重要的一点变化是2.0版本使用了自己的hprof文件解析器,不再依赖于HAHA,整个工具使用的语言也由Java切换到了Kotlin。本文结合源码对2.0版本的内存泄漏监控基本原理和hprof文件解析器实现原理做一个简单地分析介绍。
2020labs小助手
2021/03/30
1.9K0
全解系列:内存泄漏定位工具LeakCanary!
在日常开发中,不可避免的会遇到内存泄漏的问题,从而导致App的内存使用紧张,严重的情况还会导致App的卡顿甚至是奔溃,所以需要开发人员解决这些内存泄漏的问题。
胡飞洋
2020/09/17
5.7K0
全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !
大概一年以前,写过一篇 LeakCanary 源码解析 ,当时是基于 1.5.4 版本进行分析的 。Square 公司在今年四月份发布了全新的 2.0 版本,完全使用 Kotlin 进行重构,核心原理并没有太大变化,但是做了一定的性能优化。在本文中,就让我们通过源码来看看 2.0 版本发生了哪些变化。本文不会过多的分析源码细节,详细细节可以阅读我之前基于 1.5.4 版本写的文章,两个版本在原理方面并没有太大变化。
路遥TM
2021/08/31
5720
LeakCanary原理分析
概述 LeakCanary是一个开源的内存泄漏检测库,极大简化了内存泄漏的检测流程。了解其工作原理,有助于我们更好的理解Android的内存管理机制。 使用示例 在 build.gradle中添加配置: dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' releaseImplementation 'com.squareup.leakcanary:leakcanary-androi
用户1205080
2019/03/11
1.5K0
LeakCanary原理分析
由浅入深,聊聊 LeakCanary 的那些事
关于内存泄漏,Android 开发的小伙伴应该都再熟悉不过了,比如最常见的静态类间接持有了某个 Activity 对象,又比如某个组件库的订阅在页面销毁时没有及时清理等等,这些情况下多数时都会造成内存泄漏,从而对我们App的 流畅度 造成影响,更有甚者造成了 OOM 的情况。
Petterp
2023/01/31
5310
由浅入深,聊聊 LeakCanary 的那些事
LeakCanary看这一篇文章就够了
LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanary会自动显示泄漏信息。
用户1269200
2018/07/30
6.5K0
LeakCanary看这一篇文章就够了
锦囊篇|一文摸懂LeakCanary
LeakCanary想来也是我们的一个老朋友了,但是它是如何做到对我们的App进行内存泄漏分析的呢?这也是我们今天要去研究的主题了。
ClericYi
2020/06/23
7100
LeakCanary源码解析
LeakCanary : https://github.com/square/leakcanary
俞其荣
2019/07/09
7450
「Leakcanary 源码分析」看这一篇就够了
Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。
程序亦非猿
2019/08/16
7890
「Leakcanary 源码分析」看这一篇就够了
了解LeakCanary1.6.3来龙去脉--源码分析
可达性分析法 根据是否被GC Root引用确认是否是垃圾对象要被GC回收。 常见可以作为GC Root的对象有:
包子388321
2021/03/25
6080
LeakCanary源码浅析
在Android开发中最让人们头疼的就是内存泄漏了,今天来介绍一个查看内存是否泄漏的工具LeakCanary,并通过研究源码明白它是如何分析和查找存在泄漏信息的 首先送上LeakCanary文档链接:[LeakCanary中文使用说明](https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/) Part1. 知识回顾 常用工具 1. Mat 2. LeakCanary(Square) 原理
用户1337002
2018/04/18
7530
LeakCanary 原理剖析
内存泄漏是一个隐形炸弹,其本身并不会造成程序异常,但是随着量的增长会导致其他各种并发症:OOM,UI 卡顿等。
Erossssssss
2020/08/06
2.3K0
LeakCanary 原理剖析
开源框架源码解析系列(2)——LeakCanary源码解析
LeakCanary是一个开源的,可以用来检测activtiy或者fragment内存泄漏的框架,本篇我们来学习这个框架的源码。
老马的编程之旅
2022/06/22
2K0
Android内存泄漏检测利器:LeakCanary
到这里你就可以检测到Activity的内容泄露了。其实现原理是设置Application的ActivityLifecycleCallbacks方法监控所有Activity的生命周期回调。内部实现代码为
技术小黑屋
2018/09/05
1.2K0
LeakCanary源码解析
LeakCanary是Android开发中非常常用的一个内存泄漏监测和分析工具。了解其工作原理,有助于对Android的内存泄漏有更深层次的认识。
Oceanlong
2019/03/15
1K0
Android内存泄露和ANR
内存泄漏(Memory Leak)是指程序在运行过程中,由于疏忽或错误未能释放不再使用的内存,导致这部分内存无法被回收,最终可能引发应用卡顿、崩溃或系统性能下降。
进击的阿斌
2025/02/13
6660
相关推荐
LeakCanary源码分析(基于2.4版本)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验