Potato
Potato copied to clipboard
LeakCanary Principle
LeakCanary Principle
[TOC]
这篇文章介绍 LeakCanary 的原理,基于版本 2.0,kotlin。
基本使用
加入依赖即可,以使用 ContentProvider 进行自动初始化。LifeCycle 库类似, 可用于初始化sdk,三方库等。
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}
internal class LeakSentryInstaller : ContentProvider() {
override fun onCreate(): Boolean {
CanaryLog.logger = DefaultCanaryLog()
val application = context!!.applicationContext as Application
InternalLeakSentry.install(application)
return true
}
...
}
基本原理
Reference
Reference 把内存分为 4 种状态,Active,Pending,Exqueued, Inactive。
-
Active 一般说来内存一开始被分配的状态都是 Active
-
Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
-
Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
-
Inactive 最终的状态,无法变成其他的状态。
ReferenceQueue
引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中
如果检测一个对象是否被回收
需要采用 Reference + ReferenceQueue
- 创建一个引用队列 queue
- 创建 Reference 对象(通常用弱引用)并关联引用队列
- 在 Reference 被回收的时候,Reference 会被添加到 queue 中
在 Reference 类加载的时候,Java 虚拟机会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC。
Heap Dump
Heap Dump也叫堆存储文件,是一个Java进程在某个时间点上的内存快照。
原理说明
- 监听 Activity 的生命周期。
- 在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。
- 一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。
- dump 出 hprof 文件,找到泄漏路径。
初始化
// InternalLeakSentry.kt
fun install(application: Application) {
CanaryLog.d("Installing LeakSentry")
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalLeakSentry.application = application
val configProvider = { LeakSentry.config }
ActivityDestroyWatcher.install(
application, refWatcher, configProvider
)
FragmentDestroyWatcher.install(
application, refWatcher, configProvider
)
listener.onLeakSentryInstalled(application)
}
internal class ActivityDestroyWatcher private constructor(
private val refWatcher: RefWatcher,
private val configProvider: () -> Config
) {
// 监听 Activity 的生命周期
private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
override fun onActivityDestroyed(activity: Activity) {
// destroy 进行跟踪分析
if (configProvider().watchActivities) {
refWatcher.watch(activity)
}
}
}
companion object {
fun install(
application: Application,
refWatcher: RefWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(refWatcher, configProvider)
// 注册监听回调
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
上述代码很容易理解,首先是对 Activity 的生命周期进行监听,在 destory 发生的时候进行监听,看是否被回收。
监听过程
核心代码如下:
@Synchronized fun watch(
watchedReference: Any,
referenceName: String
) {
if (!isEnabled()) {
return
}
// 移除被回收的所有的弱应用对象
removeWeaklyReachableReferences()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 对 Activity 创建 弱应用对象
val reference =
KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
if (referenceName != "") {
CanaryLog.d(
"Watching instance of %s named %s with key %s", reference.className,
referenceName, key
)
} else {
CanaryLog.d(
"Watching instance of %s with key %s", reference.className, key
)
}
watchedReferences[key] = reference
checkRetainedExecutor.execute {
// 延时5秒后,如果该对象还未被回收,则发生内存泄漏。
// 当泄漏的对象5个以上时,dump 出 hprof 文件,找到泄漏路径
moveToRetained(key)
}
}
private fun removeWeaklyReachableReferences() {
// 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) {
// 如果存在,则表示已经被回收,从观察列表中移除
val removedRef = watchedReferences.remove(ref.key)
if (removedRef == null) {
retainedReferences.remove(ref.key)
}
}
} while (ref != null)
}
}
回过去看原理,还是很熔体理解。
moveToRetained() 就是后续判断泄漏对象和分析 dump 文件的工作。这里不做过多介绍。
同理, fragment 的观察过程也类似。