Leakcanary源码分析
原理利用:
当有强引用指向value内存区域时,即使进行gc,弱引用也不会被释放,对象不回被回收。
当无强引用指向value内存区域是,此时进行gc,弱引用会被释放,对象将会执行回收流程。
import java.lang.ref.WeakReference;
/**
* 弱引用回收测试
*/
public class WeakReferenceDemo {
public static WeakReference<String> weakReference1;
public static WeakReference<String> weakReference2;
public static void main(String[] args) {
test1();
//可以输出hello值,此时两个弱引用扔持有对象,而且未进行gc
System.out.println("未进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());
//此时已无强一用执行"value"所在内存区域,gc时会回收弱引用
System.gc();
//此时输出都为nuill
System.out.println("进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());
}
public static void test1() {
String hello = new String("value");
weakReference1 = new WeakReference<>(hello);
System.gc();
//此时gc不会回收弱引用,因为字符串"value"仍然被hello对象强引用
System.out.println("进行gc时,强引用与弱引用同时指向value内存区域:" + weakReference1.get());
}
}
一、Leakcanary初始化:
1.1、分析版本:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
1.2、初始化
如上图看MainProcessAppWatcherInstaller这个,利用ContentProvider的特性进行自动初始化。
该变量控制是否自动初始化
leak_canary_watcher_auto_install
AppWatcher.manualInstall(application)方法进行初始化
二、源码分析
2.1、分析AppWatcher.manualInstall(application)方法进行初始化
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
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"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}
我们要关注的就是watchersToInstall这个参数,这个参数默认使用appDefaultWatchers()方法进行赋值,走进去看下:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher //`ObjectWatcher`方法下面会提到
): List<InstallableWatcher> {
return listOf(
//Activity的监测
ActivityWatcher(application, reachabilityWatcher),
//Fragment的监测
FragmentAndViewModelWatcher(application, reachabilityWatcher),
//RootView的监测
RootViewWatcher(reachabilityWatcher),
//Service的监测
ServiceWatcher(reachabilityWatcher)
)
}
从上面的名称可以得知进行不同的监测。
2.2、分析activity的监测流程ActivityWatcher(application, reachabilityWatcher)
/**
* Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
* callback.
*/
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)
}
}
上面的by noOpDelegate()方法使用了动态代理不过里面没有写任何逻辑 所以是no op 此处相当于一个适配器方法、感兴趣的可以点进去看看。
onActivityDestroyed方法就是监测Activity销毁的方法,里面实现了reachabilityWatcher.expectWeaklyReachable:
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
fun interface ReachabilityWatcher {
/**
* Expects the provided [watchedObject] to become weakly reachable soon. If not,
* [watchedObject] will be considered retained.
翻译:期望提供的[watchdobject]很快成为弱可访问的。如果不是这样,\
[watchdobject]将被视为保留
*/
fun expectWeaklyReachable(
watchedObject: Any,
description: String
)
}
上面该方法只有一个实现:ObjectWatcher 他在上面的appDefaultWatchers方法中提到过初始化
2.3、ObjectWatcher类的expectWeaklyReachable方法预计弱可及
1.利用弱引用WeakReference和弱引用队列ReferenceQueue相绑定,被gc成功回收的对象会出现在弱引用队列ReferenceQueue中,通过方法removeWeaklyReachableObjects判断ReferenceQueue中是否存在当前对象,如果存在证明被成功回收,所以不存在泄漏,就将对象从watchedObjects观察表中移除。
2.如果gc后弱引用队列ReferenceQueue没有当前对象,则怀疑当前对象可能存在泄漏
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//注意1.1.
//将ReferenceQueue中出现的弱引用移除
//这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
// 记下观测开始的时间
val watchUptimeMillis = clock.uptimeMillis()
//注意2.1. 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器,
//这样的话,弱引用被回收的话会出现在ReferenceQueue中
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"
}
// 将key-reference保存到map中
watchedObjects[key] = reference
//注意3. ---主线程5秒之后执行moveToRetained(key)方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
先看第一点removeWeaklyReachableObjects:
将ReferenceQueue中出现的弱引用移除
这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
就会将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏 (弱引用是不会阻止 GC 回收对象的)
注释翻译:一旦WeakReferences所指向的对象变为弱引用,WeakReferences就会被加入ReferenceQueue队列可及。
这是在最终化或垃圾收集实际发生之前
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)
}
ReferenceQueue的poll方法解释说明:
blog.csdn.net/u013679744/…
看第二点KeyedWeakReference
首先我们要知道 KeyedWeakReference 继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时我们可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。
(意思是:弱引用对象会出现在ReferenceQueue队列中)
类注释翻译:一个弱引用,ObjectWatcher使用它来确定哪些对象是弱可及的,
哪些对象不是。ObjectWatcher使用key来跟踪还没有进入相关ReferenceQueue的KeyedWeakReference实例。\
heapDumpUptimeMillis应该设置为时钟的当前时间。uptimeMillis正好在转储堆之前,
这样我们就可以在以后确定一个对象被保留了多长时间。
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
) {
/**
* Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't
* been yet.
*/
@Volatile
var retainedUptimeMillis = -1L
override fun clear() {
super.clear()
retainedUptimeMillis = -1L
}
companion object {
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}
}
所以结合上面两点我们知道: expectWeaklyReachable方法的流程如下:
所以 removeWeaklyReachableObjects() 方法的作用就是将已经被 GC 的对象从 watchedObjects 集合中删除。(因为被回收的弱引用对象会出现在ReferenceQueue中)
当我们调用 expectWeaklyReachable 方法时,使用removeWeaklyReachableObjects()先清理已经入队的弱引用对象,接着将需要观察的对象,存储为一个 KeyedWeakReference 的弱引用对象,这样的话弱引用被回收的话会出现在ReferenceQueue中,再存放到 watchedObjects 集合中,最后使用 checkRetainedExecutor 安排一次 moveToRetained 任务,这个任务是怀疑当前的对象存在泄漏。
checkRetainedExecutor 是使用 Handler 实现,默认延迟 5s 执行任务。
- 将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。
- 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。
看第三点moveToRetained
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {//这个任务是怀疑当前的对象存在泄漏。加入到一个怀疑列表
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
小结:
5秒钟到了,如果它们没有内存泄漏,所以出现在了ReferenceQueue--queue队列中,然后将引用队列中出现的对象从map中移除--watchedObjects.remove(ref.key),。然后判断watchedObjects[key]对象是否还存在,如果在的话,说明可能发生了内存泄漏(因为对象不在前面的ReferenceQueue队列中)。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis字段,然后通知所有的对象,内存泄漏发生了。
2.4、objectWatcher类中的onObjectRetainedListeners泄漏回调
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {//继承接口
...
//看这个
override fun onObjectRetained() = scheduleRetainedObjectCheck()
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
//添加自己
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
...
}
...
}
InternalLeakCanary类中的invoke(application: Application)方法调用addOnObjectRetainedListener
从上可知: 看InternalLeakCanary类中的onObjectRetained()方法
scheduleRetainedObjectCheck
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
跟踪一下:HeapDumpTrigger中的scheduleRetainedObjectCheck
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() {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) {
// Before notifying that we can't dump heap, let's check if we still have retained object.
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
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"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
上面的代码就是对于内存泄漏判定的代码了:
首先进入onObjectRetained方法,该方法会调用scheduleRetainedObjectCheck方法。此方法也就是在后台线程中执行checkRetainedObjects方法来检查泄漏的对象:
- 首先获取泄漏对象的个数,如果大于0,则GC一次之后再次获取
- 如果此时泄漏对象的个数大于等于5个
注:config.retainedVisibleThreshold对象=5,则继续执行下面的代码,准备dump heap - 如果config里面配置的“调试时不允许dump heap”为false(默认值)且正在调试,则20s之后再试
- 否则可以开始dump heap:此时会先记下dump发生的时间,取消内存泄漏通知,dump heap,清除所有观测事件小于等于dump发生时间的对象(因为这些对象已经处理完毕了),最后运行HeapAnalyzerService开始分析heap。
泄漏个数计算
是如何获取泄露对象的个数的呢?我们想一下,在前面的代码中,主线程5秒之后执行了一段检测的代码,在这里面将所有泄露的对象都记下了当时的时间,存在retainedUptimeMillis字段里面。那么我们遍历所有元素,统计一下该字段不为默认值(-1)的个数即可:
注释翻译:
返回被保留的对象的数量,即被监视的对象的数量,这些对象不是弱可达的,并且被监视的时间足够长而被认为是被保留的。
Returns the number of retained objects, ie the number of watched objects that aren't weakly reachable, and have been watched for long enough to be considered retained.
val retainedObjectCount: Int
@Synchronized get() {
removeWeaklyReachableObjects()
return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
}
上面提到:如果有内存泄漏的话,会调用gcTrigger.runGc()方法,这里的gcTrigger我们提到过,是GcTrigger.Default:
/**
* [GcTrigger] is used to try triggering garbage collection and enqueuing [KeyedWeakReference] into
* the associated [java.lang.ref.ReferenceQueue]. The default implementation [Default] comes from
* AOSP.
*/
fun interface GcTrigger {
/**
* Attempts to run garbage collection.
*/
fun runGc()
/**
* Default implementation of [GcTrigger].
*/
object Default : GcTrigger {
override fun runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
GcTrigger 上面注释代码提到:System.gc()不会每次都进行垃圾收集。Runtime.gc ()更可能执行gc。
执行一次GC操作之后,下面粗暴的等待100ms,这样有足够的时间可以让弱引用移动到合适的引用队列里面。这就是GcTrigger.Default所干的事情。
GCTrigger触发GC之后,再次判断一下发生内存泄漏的对象的个数,如果仍然还有,那么肯定是泄漏无疑了,实锤!!
随后调用checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)方法,判断泄漏对象的个数是否达到了阈值,如果达到了则直接dump heap;否则发出一个内存泄漏的通知。
我们看一下这个方法:checkRetainedCount
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int,
nopeReason: String? = null
): Boolean {
// lastDisplayedRetainedObjectCount默认值为0,此处我们肯定有内存泄漏,因此countChanged为true
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
// 保存下当前的内存泄漏对象的个数
lastDisplayedRetainedObjectCount = retainedKeysCount
// 如果内存泄漏个数为0,则说明已经处理了所有的内存泄漏
if (retainedKeysCount == 0) {
if (countChanged) {
SharkLog.d { "All retained objects have been garbage collected" }
onRetainInstanceListener.onEvent(NoMoreObjects)
showNoMoreRetainedObjectNotification()
}
return true
}
val applicationVisible = applicationVisible
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
if (countChanged) {
val whatsNext = if (applicationVisible) {
if (retainedKeysCount < retainedVisibleThreshold) {
"not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)"
} else {
if (nopeReason != null) {
"would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason"
} else {
"dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)"
}
}
} else if (applicationInvisibleLessThanWatchPeriod) {
val wait =
AppWatcher.retainedDelayMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt)
if (nopeReason != null) {
"would dump heap in $wait ms (app just became invisible) but $nopeReason"
} else {
"dumping heap in $wait ms (app just became invisible)"
}
} else {
if (nopeReason != null) {
"would dump heap now (app is invisible) but $nopeReason"
} else {
"dumping heap now (app is invisible)"
}
}
SharkLog.d {
val s = if (retainedKeysCount > 1) "s" else ""
"Found $retainedKeysCount object$s retained, $whatsNext"
}
}
// 如果泄漏个数小于5个
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
// 展示一个内存泄漏发生的通知
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
// 2秒钟之后再次执行检查泄漏对象的方法,看看泄漏个数是否有变化
scheduleRetainedObjectCheck(
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
1. // 如果泄漏个数大于等于5个,返回false,则返回后checkRetainedObjects方法会继续执行
1. // 此时就会dump heap
return false
}
原理:
LeakCanary 2检测内存泄漏原理: 在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC之前,会出现在ReferenceQueue中。随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。 这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用Runtime.getRuntime().gc()进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。
参考:
源码分析参考:juejin.cn/post/711431…
源码分析参考:juejin.cn/post/684490…
弱引用对象:www.jianshu.com/p/cdb2ea379…
弱引用对象:www.jianshu.com/p/cdcc56f5b…
ReferenceQueue:blog.csdn.net/u013679744/…