LeakCanary原理分析
LeakCanary 2.7 原理分析
基础使用
这里直接CV官方文档:
To use LeakCanary, add the leakcanary-android
dependency to your app’s build.gradle
file:
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
That’s it, there is no code change needed!
Confirm that LeakCanary is running on startup by filtering on the LeakCanary
tag in Logcat:
D LeakCanary: LeakCanary is running and ready to detect leaks
原理解析
初始化
以前用过LeakCanary或者看网上的一些博客会知道以前的LeakCanary是这样初始化的:
需要在application里面自己手动进行初始化
public class App extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
refWatcher = LeakCanary.install(this);
}
}
而新版本不需要我们自己去手动初始化了。那它是怎么初始化的?我们先来看看一个应用程序的执行顺序
可以看到ContentProVider的执行顺序是优先于Application的,而LeakCanary就是实现了一个自己的ContentProvider并在它的OnCreate方法里进行了初始化
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}
来看看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"
}
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()
}
}
核心代码:
watchersToInstall.forEach {
it.install()
}
来看看watchersToInstall是什么东西,在构造方法里:
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
所以来看appDefaultWatchers(application)
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
首先看看ActivityWatcher是什么东西
/**
* 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)
}
}
Application 类提供了 registerActivityLifecycleCallbacks
方法用于注册 Activity 的生命周期监听****类,这样我们就能在 Application 中对所有的 Activity 生命周期回调中做一些统一处理。
当install方法被调用后,app中的所有avtivity只要onDestory时就会调用
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
这样LeakCanary就完成了对Activity是否发生内存泄漏的初始化,只要Activity一被摧毁
reachabilityWatcher就会就去检查这个activity是否发生了内存泄漏,至于它具体是怎么检查的后面再讨论,我们接着看 FragmentAndViewModelWatcher
/**
* Expects:
* - Fragments (Support Library, Android X and AOSP) to become weakly reachable soon after they
* receive the Fragment#onDestroy() callback.
* - Fragment views (Support Library, Android X and AOSP) to become weakly reachable soon after
* fragments receive the Fragment#onDestroyView() callback.
* - Android X view models (both activity and fragment view models) to become weakly reachable soon
* after they received the ViewModel#onCleared() callback.
*/
class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
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)
}
private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
reachabilityWatcher: ReachabilityWatcher
): ((Activity) -> Unit)? {
return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)
) {
val watcherConstructor =
Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
} else {
null
}
}
private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
true
} catch (e: Throwable) {
// e is typically expected to be a ClassNotFoundException
// Unfortunately, prior to version 25.0.2 of the support library the
// FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
// Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
// the FragmentManager.FragmentLifecycleCallbacks class, leading to the
// AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
// thrown here. So we're just covering our butts here and catching everything, and assuming
// any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
false
}
}
companion object {
private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"
// Using a string builder to prevent Jetifier from changing this string to Android X Fragment
@Suppress("VariableNaming", "PropertyName")
private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =
StringBuilder("android.").append("support.v4.app.Fragment")
.toString()
private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidSupportFragmentDestroyWatcher"
}
}
FragmentAndViewModelWatcher监控Fragment和Fragment View的泄露,而FragmentManager 类提供了 registerFragmentLifecycleCallbacks方法来提供对Fragment的生命周期监听,所以其实LeakCanary就是通过和Activity类似的方式当Fragment到达某个特定的生命周期时去检测Fragment和Fragment View是否发生了内存泄漏,在onFragmentDestroyed和onFragmentViewDestroyed中调用reachabilityWatcher的expectWeaklyReachable方法。上面的代码比Activity复杂有俩个原因
1.监听Fragment的onDestroy相对复杂点,原理是先监听Activity生命周期,然后在Activity onCreate时通过fragmentManager.registerFragmentLifecycleCallbacks注册Fragment生命周期回调。
2.Fragment有多种版本:android.app.Fragment、androidx.fragment.app.Fragment、android.support.v4.app.Fragment,而LeakCanary要做到这几种Fragment都能做到监测是否发生了内存泄漏,
对这三种Fragment进行监测的代码:
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
我们以
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
为例子进行分析
看看AndroidOFragmentDestroyWatcher
internal class AndroidOFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
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"
)
}
}
override fun invoke(activity: Activity) {
val fragmentManager = activity.fragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
当install方法被调用时,会注册fragment所对应的avtivity的回调
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
由watcher(activity) (watcher是一个高阶函数)可以知道
AndroidOFragmentDestroyWatcher的
override fun invoke(activity: Activity) {
val fragmentManager = activity.fragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
会被调用,这样LeakCanary就完成了对Fragment及其View被摧毁时的监听
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
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"
)
}
}
至此,我们已经分析了其中俩种Watcher ActivityWatcher和 FragmentAndViewModelWatcher 这四种Watcher的初始化原理都十分相似,就是去监听所监测的对象被销毁然后reachabilityWatcher去执行逻辑检测是否发生了内存泄漏
如何检测是否发生内存泄漏
四种对象引用
首先介绍下Java中的四种对象引用 强引用、软引用、弱引用和虚引用
强引用
Person person=new Person() 像这种new出来的都属于强引用,如果一个对象具有强引用,则无论在什么情况下,GC都不会回收被引用的对象。当内存空间****不足时,JAVA虚拟机宁可抛出OutOfMemoryError终止应用程序也不会回收具有强引用的对象。
软引用
如果一个对象具有软引用,在内存空间充足时,GC就不会回收该对象;当内存空间不足时,GC会回收该对象的内存
Person person=new Person();
SoftReference sr=new SoftReference(person);
弱引用
弱引用具有更短的生命.GC在扫描的过程中,一旦发现只具有被弱引用关联的对象,都会回收掉被弱引用关联的对象
Person person=new Person();
WeakReference wr=new WeakReference(person);
虚引用
虚引等同于没有引用,这意味着在任何时候都可能被GC回收
而在这里介绍四种引用是为了讲 引用队列(ReferenceQueue)
除了强引用外的其它三种引用可以和一个引用队列联合使用,如果这三种引用所引用的对象被GC回收,Java虚拟机就会把这个引用对象加入到与之关联的引用队列中。
这里注意区分引用对象和引用对象所引用的对象的区别!
LeakCannary将所监测是否发生内存泄漏的对象被一个弱引用对象所引用,并且给每一个监测的对象生成一个标识符并用一个Map进行存储,然后当所监测对象被结束生命周期时,如果这个监测对象被GC回收了,Java虚拟机就会把这个弱引用对象加入到与之关联的引用队列中,通过查询引用队列中这个引用对象就知道这个监测的对象是否被GC了,不在引用队列的软引用对象所引用的监测对象就说明没有被GC回收
这就差不多是LeakCanary的原理,我们下面通过源码看看细节:
看到reachabilityWatcher.expectWeaklyReachable方法
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
//可以看到函数的参数watchedObject的参数是Any,这意味着LeakCanary理论上其实可以检测所有类型的对象,只是官方默认为我们提供了四种默认监测实现
description: String
) {
if (!isEnabled()) {
return
}
//这是一个比较重要的方法,我们后面讲解是干什么的
removeWeaklyReachableObjects()
//随机生成一个字符串,用来给要监测的对象作标识符
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//用软引用对象引用监测对象,queue即为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"
}
//给每一个监测的对象生成一个标识符并用一个Map进行存储
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
checkRetainedExecutor实现了Executor接口
public interface Executor {
void execute(Runnable command);
}
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
}
internal val mainHandler by lazy { Handler(Looper.getMainLooper()) }
所以moveToRetained(key)运行在子线程
接着看moveToRetained(key)
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
现在看看 removeWeaklyReachableObjects方法
这个方法就是从referenceQueue里取出所有的弱引用对象,然后用这个弱引用对象去比对Map中存在的它所引用的对象然后将其删除,这样如果成功GC的被引用的对象它的弱引用对象会存在在这个referenceQueue中,那么这个被引用的对象就会通过removeWeaklyReachableObjects从Map中删除,调用完这个方法后还存在在Map中的对象就是发生内存泄漏了
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)
}
removeWeaklyReachableObjects的注释意思是弱引用对象会在它们所引用的对象变得弱可达后立即被放入引用队列中,这个过程是在GC实际发生前的
接下来我们看看如果发生了内存泄漏LeakCanary会怎么处理
这一行是关键代码
onObjectRetainedListeners.forEach { it.onObjectRetained() }
先看看onObjectRetainedListeners是什么
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
@Synchronized fun removeOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.remove(listener)
}
在InternalLeakCanary的invoke方法中:
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
onObjectRetainedListeners实际上是InternalLeakCanary
所以看到InternalLeakCanary的onObjectRetained方法
InternalLeakCanary#onObjectRetained
override fun onObjectRetained() = scheduleRetainedObjectCheck()
InternalLeakCanary#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)
}
在heapDumpTrigger#scheduleRetainedObjectCheck中
关键代码
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
backgroundHandler是一个从后台线程往主线程发送Looper的Handler
//HandlerThread是一个继承自Thread的类
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
val backgroundHandler = Handler(handlerThread.looper)
说明checkRetainedObjects()运行在主线程
heapDumpTrigger#checkRetainedObjects
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) {
// 可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。
// 这时候应该主动调用一次 GC,可能可以避免一次 heap dump
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
//checkRetainedCount() 函数判断当前泄露实例个数如果小于 5 个,仅仅只是给用户一个通知,不会进行heap dump 操作
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"
)
}
heapDumpTrigger#dumpHeap
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
//heapDumper.dumpHeap()导出了堆文件
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
//这里对堆文件进行分析
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
Ref:
Android内存泄漏检测之LeakCanary2.0(Kotlin版)的实现原理 - 知乎 (zhihu.com)
全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 ! (juejin.cn)
看完这篇 LeakCanary 原理分析,又可以虐面试官了! (qq.com)
007 LeakCanary 内存泄漏原理完全解析 (juejin.cn)