引用类型 GC 时机
强引用 平时写代码最常用的引用类型,对象只要被强引用就不会被 GC
软引用 SoftReference 只有当内存不足时才会被 GC
弱引用 WeakReference 会被正常 GC
虚引用 PhantomReference 会被正常 GC,因为 get() 总是返回 null,一般用来跟踪对象的生命周期
public abstract class Reference<T> {
    /* -- Constructors -- */

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = queue;
    }
}

所有的引用类型都可以在构造时与一个 ReferenceQueue 关联,当引用的对象被 GC 后,这个 Reference 将被入队到关联的引用队列里

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

  // 持有被监控对象的弱引用,以便后续的检查
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

  // 引用队列,被 GC 的对象的引用会进入此队列
  private val queue = ReferenceQueue<Any>()
}

如何检测泄漏对象

ObjectWatcher 实现了 LeakCanary 的泄漏检测机制:监控 - 等待 - 检查

用 WeakReference + ReferenceQueue 监控对象的 GC 状态,并用 watchedObjects 持有它的弱引用,key 是 UUID

将指定对象交由 ObjectWatcher 进行监控

// 监控检测对象
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  removeWeaklyReachableObjects()  // 从 watchedObjects 移除已被 GC 的对象

  // 弱引用监控对象并放入 watchedObjects
  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

  // 选机检查(默认 5s 后执行检查函数 moveToRetained)
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

// 出现在 queue 里的对象已被成功 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)
}

检查是否发生泄漏,默认情况是等待 5s,让 GC 线程有足够的机会去发现并回收这个对象,如果 5s 后仍然没有被 GC(没有出现在引用队列里),那么可以证明这个对象发生了内存泄漏,被强引用导致存活超过它的生命周期

// 上面说过检查操作将提交给 checkRetainedExecutor 执行
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  // ...
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

// 而 checkRetainedExecutor 是通过构造函数传入的
class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val isEnabled: () -> Boolean = { true }
)

// 默认是等待 5s 并在主线程执行检查操作
object AppWatcher {
  private const val RETAINED_DELAY_NOT_SET = -1L
  @Volatile private var retainedDelayMillis = RETAINED_DELAY_NOT_SET

  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)  // 在主线程执行检查
    },
    isEnabled = { true }
  )

  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),   // 默认等待 5s
    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
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }
}

// 如果没有出现在引用队列里,说明此对象已发生泄漏,发出通知
@Synchronized private fun moveToRetained(key: String) {
  removeWeaklyReachableObjects()    // 出现在引用队列里说明对象已被 GC,可以从 watchedObjects 移除
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

// 收集 destroyed Activity
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"
        )
      }
    }
}

检测 Activity 泄漏

通过 ActivityLifecycleCallbacks.onActivityDestroyed 可以收集到 destoryed Activity,这些 Activity 已走完它的生命周期,应该被后续的 GC 回收掉

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    // 发现 View
    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)"
        )
      }
    }

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

检测 Fragment 和 View 的泄漏

利用 FragmentLifecycleCallbacks 发现被 destroyed Fragment 和 View,然后用 ObjectWatcher 监控是否发生泄漏

internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
    // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
    // does not have ViewModelStore#keys. All versions currently have the mMap field.
    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
    }
  }

  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
    ) {
      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)
    }
  }
}

检测 ViewModel 泄漏

Fragment 里的 ViewModel 则是在 FragmentLifecycleCallbacks.onFragmentCreated 时,注入一个 ViewModel,通过反射拿到 ViewModelStore.mMap,这里有所有的 ViewModel,在 ViewModel.onCleared 时把它们加入 ObjectWatcher 进行泄漏检查

class MyService : Service {
  // ...
  override fun onDestroy() {
    super.onDestroy()
    AppWatcher.objectWatcher.watch(
      watchedObject = this,
      description = "MyService received Service#onDestroy() callback"
    )
  }
}

检测更多类型的泄漏

对 Service 的检查就比较 hack 了,通过反射替换 ActivityThread.mH.mCallback,通过 Message.waht == H.STOP_SERVICE 定位到 ActivityThread.handleStopService 的调用时机,然后把这个被 stop 的 Service 记录下来;用动态代理实现 IActivityManager 并替换掉 ActivityManager.IActivityManagerSinglteon.mInstance,从而拦截方法 serviceDoneExecuting,此方法的调用表示 Service 生命周期已完结,可以把它交由 ObjectWatcher 进行监控

这给我启示,对于我们感兴趣的对象(需要警惕泄漏的对象,比如 Bitmap),都可以通过 ObjectWatcher 去检测泄漏问题

Heap Dump

发现对象泄漏后触发 OnObjectRetainedListener.onObjectRetained(),最终调用 Debug.dumpHprofData 生成 hprof 文件

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {

  override fun onObjectRetained() = scheduleRetainedObjectCheck()

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

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

  private fun checkRetainedObjects() {
    // ...
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      // ...
    }
  }
}

internal class AndroidHeapDumper {
  override fun dumpHeap(): DumpHeapResult {
    // ...
    val durationMillis = measureDurationMillis {
      Debug.dumpHprofData(heapDumpFile.absolutePath)
    }
    // ...
  }
}

解析 hprof 文件 - hprof 文件格式

java_hprof.png

android_hprof.png

hprof_header.png

hprof 是结构紧凑的二进制文件,整体上分为 HeaderRecord 数组两大部分

Header 总共 18 + 4 + 4 + 4 = 32 字节,包括:

  1. 格式名和版本号:JAVA PROFILE 1.0.3(18 字节)
  2. 标识符大小(4 字节)
  3. 高位时间戳(4 字节)
  4. 地位时间戳(4 字节)

hprof_record.png

Record 数组记录了内存中的各种数据

  1. TAG,Record 的类型(1 字节)
  2. TIME,时间戳(4 字节)
  3. LENGTH,Record BODY 的长度(4 字节)
  4. BODY,不同的 Record 类型有不同的 BODY

支持的 TAG 类型主要有: