LeakCanary原理分析
简介
使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary
总结来说LeakCanary是一个基于MAT用来检测内存泄漏的一个有效的简单好用的工具。
不足
申请大容量内存导致的OOM问题、Bitmap内存未释放问题,Service 中的内存泄漏无法检测等,需要我们用Mat。
常见的内存泄漏
暂时不展开以后再去做这块的介绍其实总结出来都大同小异
常见的内存泄漏和解决方式1
常见的内存泄漏和解决方式2
核心原理
SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,比如说:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用放到与之关联的ReferenceQueue中,所以我们如果在对象该回收的时候去检测队列如果发现队列中有这个对象的若应用说明这个对象被正常回收了,如果检测是发现没有对象的弱引用不在说明对象没有被正常回收就是发生了内存泄漏。但是实际操作中在我们认为对象该被回收的时候实际上可能有一定的延迟或者并没有发生GC,所以需要我们给他一定的回收时间和重试时间或者手动触发一下GC(不一定执行)。
使用
2.0之后只需要在gradle里添加一个这个就可以了 2.4都是用kotlin写的。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
注册install的工作都放到AppWatcherInstaller中了。
AppWatcherInstaller这个类它继承自ContentProvider,四大组件之一。
在leakcanary-object-watcher-android子module中leakcanary/leakcanary-object-watcher-android/src/main/AndroidManifest.xml中
已经注册过了AppWatcherInstaller$MainProcess,MainProcess是AppWatcherInstaller他的子类。AppWatcherInstaller是个密封类。
密封类特性:
- 声明一个密封类在类名使用 sealed 修饰符
- 所有子类都必须在与密封类自身相同的文件中声明(子类的扩展类不受此控制)
- 一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员
- 密封类不允许有非-private 构造函数(其构造函数默认为 private)
/**
* Content providers are loaded before the application class is created. [AppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
*/
internal sealed class AppWatcherInstaller : ContentProvider() {
...省略代吗
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
...省略代吗
}
//注册
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
ContentProvider的特点就是不用显示调用初始化,只需要在AndroidManifest.xml中注册好,在执行完application的onCreate之前,就会调用ContentProvider的onCreate()方法。正是利用这一点,LeakCanary把注册写在了这里面,有系统自动调用完成,对开发者完全无感知。
这里也可以看出我们了解四大组件源码Android启动流程的重要性。我们可以做很多优化的事情。
原理
何时检测泄漏
首先我们要先了解LeakCanary-Android主要是检测Activity和Fragment的内存泄漏。其他的对象的泄漏需要我们自己去
我们带着问题先去看下他的注册源码。
跟踪AppWatcherInstaller中注册的代码到AppWatcher
AppWatcher.manualInstall(application)
object AppWatcher {
.....省略代码
/**
* [AppWatcher] is automatically installed in the main process on startup. You can
* disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:
*
* ```
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_watcher_auto_install">false</bool>
* </resources>
* ```
*
* If you disabled automatic install then you can call this method to install [AppWatcher].
*/
fun manualInstall(application: Application) {
InternalAppWatcher.install(application)
}
}
继续追踪代码 InternalAppWatcher.install(application)到InternalAppWatcher
/**
* Note: this object must load fine in a JUnit environment
*/
internal object InternalAppWatcher {
...省略代吗
fun install(application: Application) {
...省略代吗
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
...省略代吗
}
...省略代吗
}
InternalAppWatcher中做了ActivityDestroyWatcher.install
FragmentDestroyWatcher.install
我们继续追踪到FragmentDestroyWatcher和ActivityDestroyWatcher的代码细节。
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
//创建Activity生命周期监听对象 监听Activity destroy方法然后调用 objectWatcher
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
//判断config中配置是否监听activity泄漏
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
companion object {
//伴生对象中install方法中创建ActivityDestroyWatcher对象并注册Activiy监听传入ObjectWatcher ,Config
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
Fragment 则要复杂一些FragmentDestroyWatcher中的install中在Activity启动的onCreate监听中通过Activity获取FragmentManager,通过FragmentManager.registerFragmentLifecycleCallbacks注册监听,主要是在onFragmentViewDestroyed 和 onFragmentDestroyed方法中监听 ,onFragmentViewDestroyed中主要是对fragment.view的泄漏检测。
其中对AndroidO 以及 AndroidX和AndroidSupport库中的Fragment进行了适配:
AndroidOFragmentDestroyWatcher
AndroidXFragmentDestroyWatcher
AndroidSupportFragmentDestroyWatcher
此处限于篇幅不列出具体代码要看的可以自己导入下leakCanary去具体看下代码。
如何检测泄漏
通过上面的分析我们发现Fragment和Activity都是通过监听destroy方法然后,去执行ObjectWatcher的watch方法,那看来核心的检测代码就在里边了。那我们现在就去探索一下吧。
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
追踪objectWatcher.watch方法。
追踪过程中你可能需要了解一下强软弱虚四种引用
其中包含了我的一个疑问:Activity或者Application被弱引用包装为啥GC的时候不会被回收?
Android中使用比较常见,我们经常会碰到需要使用Context及其子类如Application、Activity等组件类的时候,这时候如果使用强引用很容易出现内存泄漏的情况,而使用软引用又会造成不必要的资源浪费,因此此时使用弱引用是最好的解决方案,因为像Activity这类自带生命周期的组件都是被更底层的对象持有着强引用的,如AM(ActivityManager),因此当这类对象还处在自己的生命周期之中时是不会被轻易回收的,只有在生命周期结束之后才会被GC判定为只存在弱引用而最终被回收掉。
也就是说当一个对象被强引用和弱引用同时引用的时候,GC时是不会被回收的,只有在生命周期结束之后才会被GC判定为只存在弱引用而最终被回收掉。
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true }
) {
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}
/**
* Watches the provided [watchedObject].
*
* @param description Describes why the object is watched.
*/
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//先清除一次防止重复添加(应该是这么个作用因为key每次都是随机生成的)
removeWeaklyReachableObjects()
//生成每个被观察对象的key
val key = UUID.randomUUID()
.toString()
//主要是在dump的时候使用计算一个对象被引用了多久
val watchUptimeMillis = clock.uptimeMillis()
//KeyWeakReference是WeakRefrence的子类,LeakCanary的原理就是如果对象被回收了的话
//会把引用对象放到 queue(ReferenceQueue<Any>) 生成一个对watchedObject的弱引用
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"
}
//把相应的refrece放到被观察对象map中,watchedObjects可变map
watchedObjects[key] = reference
//然后通过Handler发送延迟五秒消息去判断是否还有引用没回收并dump,下边会介绍checkRetainedExecutor对象的定义
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
//poll出queue中的被回收的对象移除watchedObjects中的相应弱引用对象
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)
}
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
//handler延迟五秒后执行run方法中的moveToRetained方法
@Synchronized private fun moveToRetained(key: String) {
//先去poll出queue中的被回收的对象移除watchedObjects中的相应弱引用对象 防止5秒过程中可能对象已经被回收了 下边还要进行一次gc和dump
removeWeaklyReachableObjects()
//拿出当前watch对象的弱引用
val retainedRef = watchedObjects[key]
//【标记最后一步】若该对象若引用不为空则继续下一步为null则认为已被回收了不作处理
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//执行onObjectRetainedListeners中每个监听的onObjectRetained方法
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
}
checkRetainedExecutor和对Activity,Fragment的检测ObjectWatch
都是定义在InternalAppWatcher中的前边有说到过这个类。
private val checkRetainedExecutor = Executor {
//AppWatcher.config.watchDurationMillis
//在AppWatcher的Config类中定义val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),默认五秒
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { true }
)
接着代码中分析的最后一步(有【标记最后一步】标记的地方 handler的五秒信息后清除后引用对象不为null),
我们直接跟踪ObjectWatcher中的addOnObjectRetainedListener代码
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
鼠标放到这个方法上边 Command + 触摸板点击,我们会发现我们到了InternalLeakCanary的invoke方法中但是这个地方我们从前边分析过来也没发现那里调用了
override fun invoke(application: Application) {
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
//创建一个后台子线程的Handler,backgroundHandler这个后边是用来检测未回收对象的
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
}
但是不要急他肯定在哪里被调用了,我们看的时候都注意在主流程上边,忽略了这个没关系我们再回头去看下到底哪里使用了它,经过在前边AppWatcherInstaller,AppWatcher,InternalAppWatcher
最后我们在InternalAppWatcher发现他是通过反射调用了
internal object InternalAppWatcher {
private val onAppWatcherInstalled: (Application) -> Unit
...省略代吗
init {
//我们创建InternalAppWatcher时init方法就会调用,onAppWatcherInstalled是kotlin中invoke约定的应用可以将invoke函数的lambda表达式赋值给一个变量,
Kotlin的约定有很多种,而比如使用便捷的get操作,以及重载运算符等等,invoke约定也仅仅是一种约定而已;
我们可以把lambda表达式或者函数直接保存在一个变量中,然后就像执行函数一样直接执行这个变量,
这样的变量通常声明的时候都被我们赋值了已经直接定义好的lambda,或者通过成员引用而获取到的函数;
但是别忘了,在面向对象编程中,一个对象在通常情况下都有自己对应的类,那我们能不能定义一个类,
然后通过构造方法来产生一个对象,然后直接执行它呢?这正是invoke约定发挥作用的地方。
我们只需要在一个类中使用operator来修饰invoke函数,这样的类的对象就可以直接像一个保存lambda表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。
我们还有另一种方式来实现可调用的对象,即让类继承自函数类型,然后重写invoke方法。
val internalLeakCanary = try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null)
} catch (ignored: Throwable) {
NoLeakCanary
}
@kotlin.Suppress("UNCHECKED_CAST")
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}
fun install(application: Application) {
...省略代吗
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
//在install的时候通过onAppWatcherInstalled调用InternalLeakCanary invoke方法
onAppWatcherInstalled(application)
}
...省略代吗
}
Kotlin中init代码块和构造方法以及伴生对象中代码的调用时机及执行顺序
好了经过上变得分写我们已经知道InternalLeakCanary在哪里创建并注册对象未回收监听(addOnObjectRetainedListener)。
接下来我们分析InternalLeakCanary实现了监听方法后方法里调用了什么。
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
fun onDumpHeapReceived(forceDump: Boolean) {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onDumpHeapReceived(forceDump)
}
}
继续跟踪
//调用
heapDumpTrigger.onObjectRetained()
//onObjectRetained方法的实现
fun onObjectRetained() {
scheduleRetainedObjectCheck(
reason = "found new object retained",
rescheduling = false
)
}
分析scheduleRetainedObjectCheck:
private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
return
} else {
val verb = if (rescheduling) "Rescheduling" else "Scheduling"
val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
SharkLog.d { "$verb check for retained objects${delay} because $reason" }
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
//在后台子线程的handler中检测未回收对象
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects(reason)
}, delayMillis)
}
然后在分析checkRetainedObjects:
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
//配置中是否要dump堆栈信息并通知
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
//目前还有多少未回收对象
var retainedReferenceCount = objectWatcher.retainedObjectCount
//如果还有未回收对象则在进行一次GC不一定能执行确保对象能回收的都回收了,然后再去赋值retainedReferenceCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//检测retainedReferenceCount是否为零或者小于retainedVisibleThreshold = 5如果小于则跳过下边的检测延迟 WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L 2秒 重新从scheduleRetainedObjectCheck重新开始检测,一直循环,直到大于等于5个或者等于0个,为了防止频发回收堆造成卡顿。为零的话就直接显示通知。
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
//大于5个后,如果处于debug模式,会再等20秒,再次执行scheduleRetainedObjectCheck操作。防止debug模式会减慢回收
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
onRetainInstanceListener.onEvent(DebuggerIsAttached)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_debugger_attached
)
)
scheduleRetainedObjectCheck(
reason = "debugger is attached",
rescheduling = true,
delayMillis = WAIT_FOR_DEBUG_MILLIS
)
return
}
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
//距离上次dumpheap的时间小于WAIT_BETWEEN_HEAP_DUMPS_MILLIS =60_000L 1分钟 则继续去检测未回收对象直到大于一分钟
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(
reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
rescheduling = true,
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
//显示未回收对象个数通知隐藏
dismissRetainedCountNotification()
//dump堆栈信息
dumpHeap(retainedReferenceCount, retry = true)
}
总结一下就是:
1)如果未回收对象个数小于5,不做操作等待5秒再次进行检查未回收的个数,一直循环,直到大于等于5个或者等于0个,为了防止频发回收堆造成卡顿。
2)大于5个后,如果处于debug模式,会再等20秒,再次执行scheduleRetainedObjectCheck检测操作。防止debug模式会减慢回收
3)距离上次堆栈分析是否大于等于1分钟,如果没有超过一分钟,也需要再次延迟(1分钟-当前距离上次的时间)再次循环执行scheduleRetainedObjectCheck检测操作
6、如果上面的条件都符合了,就可以开始进行堆栈的分析了
1)、获取到内容文件 Debug.dumpHprofData(heapDumpFile.absolutePath)
2)、objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)该操作是去掉以前已经分析过的对象,也就是去除掉之前的没有回收掉的对象,不在本次分析范围内
3)、HeapAnalyzerService开启IntentService服务进行分析
4)、把结果插入数据库(泄漏区分了应用本身的内存泄漏和类库的内存泄漏),并且发送通知
好了 到这里LeakCanary的原理和源码分析完毕