内存泄漏与LeakCanary原理解析

1,596 阅读8分钟

前言

日常开发中,我们可能会遇到一些奇怪的问题和OOM,然后经过排查后发现是内存泄漏引起,那么我们如何避免内存泄漏呢?除了我们平时写代码多避免一些会导致内存泄漏的写法外,我们还可以用LeakCanary,它能够让我们在开发阶段去发现内存泄漏,进而避免把可能发生的内存泄漏带到线上去,同时它也是一个值得学习的框架。下面我们一起深呼一口气,通过源码学习它是如何对内存泄漏的监测与分析。

正文

在学习它的原理之前,我们先了解两个问题:

1.对象是在什么条件下被回收的?

简单的理解就是一个对象不再被使用了,则可以对其进行回收。判断一个对象是否可以被回收,目前有两种不同的机制,第一种是通过判断该对象的被引用次数,如果引用次数为0,则可以回收,但这样会存在的问题是如果两个对象都不再被使用了,但它们互相引用着,导致彼此的引用数都不为0,不会被回收。另一种方式是可达性分析,即从GCRoot开始,判断对象是否由root可达,不可达则可以进行回收,这样就可以避免上面说等情况,也是目前用得比较多的机制。GCRoot可以是一些应用级的变量或常量等。

2.什么是内存泄露?

当一个对象不再被使用了,但却没办法被回收的情况,例如一个Activity被销毁了,但它被其他引用持有着,导致虚拟机没办法对其进行回收。内存泄露会导致一些隐患,而且加大了发生OOM的概率。

LeakCanary的大致原理是通过监听特定对象的某个生命周期回调,然后将其创建一个弱引用并且与队列关联,5s后触发GC、再从队列检查是否有该弱引用(有表示对象被回收),如果弱引用不在队列里,则可能发生内存泄漏了,需进一步分析。

1.安装

加载该框架很简单,直接依赖即可:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

debugImplementation是因为我们只需在开发阶段使用。那么为什么直接依赖后,无需代码调用呢?

internal sealed class AppWatcherInstaller : ContentProvider() {
​
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
}

相信有的读者已经猜到了使用ContentProvider,没错,它就是注册了AppWatcherInstaller,然后在应用启动的时候,去执行了 AppWatcher.manualInstall(application),进而对整个SDK的启动。

@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  //1.
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  checkMainThread()
  if (isInstalled) {
    throw IllegalStateException(
      "AppWatcher already installed, see exception cause for prior install call", installCause
    )
  }
  check(retainedDelayMillis >= 0) {
    "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
  }
  installCause = RuntimeException("manualInstall() first called here")
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // Requires AppWatcher.objectWatcher to be set
  //2.
  LeakCanaryDelegate.loadLeakCanary(application)
​
  //3.
  watchersToInstall.forEach {
    it.install()
  }
}

1.watchersToInstall是默认赋值的参数,是内置的一些watcher。

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

从命名可以大概猜测到是对Activity、Fragment、ViewModel、View、Serivce对象的观察。

2.主要是拿到一个listener,当有泄漏的时候,会进行回调,后面会讲到。

3.分别对各个watcher进行install,其实也是开始对各种要观察的对象进行一个监听和监测

2.监测

2.1 ActivityWatcher
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
​
  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)
  }
​
  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

代码比较简单,通过ActivityLifecycleCallbacks,监听所有Activity的onDestroy,当activity的onDestroy调用之后,便会通过reachabilityWatcher开始监测是否内存泄漏,reachabilityWatcher后面会统一讲到。

2.2 FragmentAndViewModelWatcher
class FragmentAndViewModelWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
  
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    }
​
  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }
​
  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
​
}

可以看到,一样是监听Activity的生命周期,不过这次是onActivityCreated,也就是在Activity的onCreate生命周期中,去监听这个Acitivty中的Fragment。我们看看fragmentDestroyWatchers是怎么获取的:

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
​
  //1.
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
​
  //2.
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
​
  //3.
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}
  1. 如果版本大于等于O(26),则添加AndroidOFragmentDestroyWatcher
  2. 检查是否支持AndroidX(通过反射androidx.fragment.app.Fragment看是否能给成功),是的话添加AndroidXFragmentDestroyWatcher
  3. 跟第二点一样,也是通过反射来看是否支持(android.support.v4.app.Fragment),是的话则反射获取AndroidSupportFragmentDestroyWatcher。

AndroidOFragmentDestroyWatcher跟AndroidSupportFragmentDestroyWatcher差不多,分别是通过fragmentManager和supportFragmentManager监听Fragment的onDestroyView和onDestroy。 我们重点看一下AndroidXFragmentDestroyWatcher,它还包含了对viewModel的监测:

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
​
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
​
    //2.
    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }
​
    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"
      )
    }
  }
​
  //1.
  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}
  1. 可以看到,除了注册监听Fragment的生命周期以外,还出现了ViewModelClearedWatcher,用于监听属于Activity级别的ViewModel。
  2. 同时在Fragment的onCreate中,会通过ViewModelClearedWatcher去监听Fragment级别的ViewModel。
2.3 ViewModelClearedWatcher:
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {
​
  private val viewModelMap: Map<String, ViewModel>?
​
  init {
    //1.
    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
    }
  }
​
  //2.
  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }
​
  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      //3.
      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)
    }
  }
}

从代码中可以看到在install中,以传进来的activity/fragment作为storeOwner,然后去创建一个ViewModel,在onCleared去检测storeOwner的其他ViewModel是否内存泄漏。

  1. 通过反射获取storeOwner的其他ViewModel(map)。
  2. 在ViewModel调用onClear时,检测storeOwner的其他ViewModel是否内存泄漏。
  3. 创建一个ViewModelClearedWatcher对象,并且传进去storeOwner跟reachabilityWatcher

2.4 RootViewWatcher

class RootViewWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
​
  private val listener = OnRootViewAddedListener { rootView ->
    //省略判断trackDetached的代码
    if (trackDetached) {
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
​
        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }
​
        override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }
​
        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }
​
  override fun install() {
    Curtains.onRootViewsChangedListeners += listener
  }
​
  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}

RootViewWatcher主要通过添加监听起到Curtains中,监听器是在windows rootview变化的时候进行回调,进而对rootView进行监听,在onViewDetachedFromWindow时对rootView开始进行内存泄漏的检测。 下边是对windowType的检测显示,分别是Dialog、Toast、ToolTip和未知类型。

val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          is Activity -> false
          is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          // Probably a DreamService
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }
2.5 ServiceWatcher
override fun install() {
  //1.检查线程
  checkMainThread()
  check(uninstallActivityThreadHandlerCallback == null) {
    "ServiceWatcher already installed"
  }
  check(uninstallActivityManager == null) {
    "ServiceWatcher already installed"
  }
  try {
    //2,替换callback
    swapActivityThreadHandlerCallback { mCallback ->
      uninstallActivityThreadHandlerCallback = {
        swapActivityThreadHandlerCallback {
          mCallback
        }
      }
      Handler.Callback { msg ->
        if (msg.what == STOP_SERVICE) {
          val key = msg.obj as IBinder
          activityThreadServices[key]?.let {
            onServicePreDestroy(key, it)
          }
        }
        mCallback?.handleMessage(msg) ?: false
      }
    }
    //3.动态代理
    swapActivityManager { activityManagerInterface, activityManagerInstance ->
      uninstallActivityManager = {
        swapActivityManager { _, _ ->
          activityManagerInstance
        }
      }
      Proxy.newProxyInstance(
        activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
      ) { _, method, args ->
        if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
          val token = args!![0] as IBinder
          if (servicesToBeDestroyed.containsKey(token)) {
            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" }
  }
}

ServiceWatcher代码比较多,成员变量是通过反射获取的,例如其中activityThreadServices是通过反射ActivityThread拿到它的mServices成员。然后主要进行一些消息跟生命周期的hook,我们先从install开始分析。

  1. 检查是否在主线程
  2. 替换Handler的Callback,用于收到STOP_SERVICE消息时,将service记录到servicesToBeDestroyed这个map中(注意是使用一个弱引用包起来)。在uninstall时,用uninstallActivityThreadHandlerCallback替换回来原来的callback。
  3. 通过动态代理,hook住ActivityManager,在调用serviceDoneExecuting方法时,做了一些额外的逻辑,即判断第一个参数是否能够在之前的servicesToBeDestroyed中获取到对应的service,有的话,则在onServiceDestroyed方法中开始监测service是否内存泄漏。最后在uninstall中,会通过uninstallActivityThreadHandlerCallback进行替换回原来的activityManager实例。

3.分析泄漏对象

每中对象的监测,都是通过ObjectWatcher的expectWeaklyReachable开始的:

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  //1.移除
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  //2.生成一个监测对象的弱引用
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  //放到map里
  watchedObjects[key] = reference
  //3.默认是5秒后执行此任务
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

我们可以看到expectWeaklyReachable代码不多,下面根据注释的点进行分析:

  1. 移除的是之前一些被回收的监测对象。
  2. 创建一个弱引用,与queue绑定,方便观察。当该对象被gc后,会将其包装的弱引用添加到该队列中。
  3. 5秒后执行真正的监测逻辑。

moveToRetained方法:

@Synchronized private fun moveToRetained(key: String) {
  //1.
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    //2.
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}
  1. 移除的是之前一些被回收的监测对象。
  2. 通过key从map中查询是否还存在对应弱引用。
  3. 如果还存在,则代表可能存在内存泄漏了,通过对象内存泄漏到listener回调出去。

那么listener收到泄漏回调后,会做什么事情呢?我们回到最开始manualInstall方法里的那行代码:LeakCanaryDelegate.loadLeakCanary(application)

//LeakCanaryDelegate
  val loadLeakCanary by lazy {
    try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }

可以看到是通过反射获取到InternalLeakCanary的实例,然后进行调用:

override fun invoke(application: Application) {
  //省略部分代码
  AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
  //省略部分代码设置各个变量,dump内存时需要用到
   heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
    )
}

我们可以看到,其实就是将InternalLeakCanary添加到objectWatcher的监听列表里边。

InternalLeakCanary的onObjectRetained方法执行的是scheduleRetainedObjectCheck:

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

进一步跟进heapDumpTrigger的scheduleRetainedObjectCheck方法可以知道里边postDelay一个任务,执行了checkRetainedObjects方法:

private fun checkRetainedObjects() {
  //省略部分代码,主要是用于处理不dump内存快照有异常持有的对象
​
  var retainedReferenceCount = objectWatcher.retainedObjectCount
  //1.如果有异常被持有的对象,触发gc
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }
​
  //2.判断数量是否达到阀值,还有当前app的状态
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
​
  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  //3.检查距离最后一次的时间
  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(
      delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }
​
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  //4.dump内存快照
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

dump内存之前,还需做一些操作:

  1. 如果异常被持有的对象数量大于0,则触发一下gc
  2. 判断当前异常数量是否达到阀值
  3. 判断当前距离上一次dump的时间,如果小于60S,则不进行dump
  4. 以上都检查通过后,开始进行dump

4. dump内存快照

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  when (val heapDumpResult = heapDumper.dumpHeap()) {
    is NoHeapDump -> {
      //检查是否retry、还有发送通知
    }
    is HeapDump -> {
      lastDisplayedRetainedObjectCount = 0
      lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
      //清理掉在dump之前的异常对象
      objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
      //开启HeapAnalyzerService进行内存分析
      HeapAnalyzerService.runAnalysis(
        context = application,
        heapDumpFile = heapDumpResult.file,
        heapDumpDurationMillis = heapDumpResult.durationMillis,
        heapDumpReason = reason
      )
    }
  }
}

根据代码我们可以知道,dump的跟做是交给了heapDumper去做的,它是一个AndroidHeapDumper的对象。

  • dumpHeap主要逻辑是创建一个prof文件,然后通过Debug.dumpHprofData生成内存快照。
  • 如果失败的话,会检查是否去重试跟发送通知。
  • 如果成功,会清理掉objectWatcher中dump之前的异常对象,然后开启HeapAnalyzerService进行内存分析。

5. 内存分析

内存分析是使用了shark模块,核心步骤是读取hprof文件,找到之前标记的泄漏对象,寻找GcRoot,然后将分析结果保存到数据库中,我们点击通知栏进行跳转后便可以看到对象数据的展示。

结语

到这里本文的分享就结束了,内容比较多,但好的代码值得花时间去学习,因为从中可以借鉴到一些技巧跟实现思路,并且我们可以参考去自研一个类似的内存泄漏监测工具。最后给每一位读到这里的读者一个赞,同时也希望读者能够通过点赞对我给予支持。

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿