1 引言
众所周知,LeakCanary是一个内存泄漏检测的工具。那么,内存泄漏是如何定义的,通常有哪些情况呢? 在分析源代码之前,我们先弄清楚这两个问题。这两个问题在LeakCanary - Fundamentals中有提及。
2 什么是内存泄漏 (What is a memory leak?)¶
In a Java based runtime, a memory leak is a programming error that causes an application to keep a reference to an object that is no longer needed. As a result, the memory allocated for that object cannot be reclaimed, eventually leading to an OutOfMemoryError crash.
在基于Java的运行时中,内存泄漏是一种编程错误,它导致应用程序保留对不再需要的对象的引用。这会导致为该对象分配的内存无法回收,最终导致OutOfMemoryError崩溃。
For example, an Android activity instance is no longer needed after its onDestroy() method is called, and storing a reference to that activity in a static field would prevent it from being garbage collected.
例如,在Activity的onDestroy()方法被调用后,Android activity的实例就不再需要了,此时在静态字段中存储该Activity的引用将阻止其被回收。
3 常见的内存泄漏 (Common causes for memory leaks)
Most memory leaks are caused by bugs related to the lifecycle of objects. Here are a few common Android mistakes:
大多数内存泄漏是由与对象生命周期相关的错误引起的。以下是一些常见的Android错误:
-
Storing an Activity context as a field in an object that survives activity recreation due to configuration changes.
将Activity上下文存储为对象中的字段,这样配置更改后activity会重新创建,但是老的仍然存活。
-
Registering a listener, broadcast receiver or RxJava subscription which references an object with lifecycle, and forgetting to unregister when the lifecycle reaches its end.
对有生命周期的对象注册了监听器、广播接收者或者RxJava的订阅,且当对象的生命到达了终点时没有取消注册。
-
Storing a view in a static field, and not clearing that field when the view is detached.
在静态字段中存储了view,且当view detach时没有清空静态变量。
4 LeakCanary2如何检测内存泄漏
LeakCanary v2 与 v1 的代码由略微的不同,下面是两者的检测内存泄漏的原理。
LeakCanary 1.5.1 检测内存泄漏原理:
在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。 随后,会向主线程的MessageQueue添加一个IdleHandler,用于在idle时触发一个发生在HandlerThread的等待5秒后开始检测内存泄漏的代码。 这段代码首先会判断是否对象出现在引用队列中,如果有,则说明没有内存泄漏,结束。否则,调用Runtime.getRuntime().gc()进行GC,等待100ms后再次判断是否已经出现在引用队列中,若还没有被出现,那么说明有内存泄漏,开始dump hprof。
LeakCanary 2.0 beta 3 检测内存泄漏原理:
在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。 随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。 这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用Runtime.getRuntime().gc()进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。
4.1 LeakCanary2初始化
LeakCanary 2.0使用非常简单,只需要在build.gradle中配置一下,无需在项目中添加任何代码。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3'
}
原理就是利用ContentProvider的特性,其onCreate方法会在Application的onCreate方法之前被系统调用。所以只需要在AndroidManifest.xml中配置一下这个ContentProvider,然后在onCreate方法中进行初始化即可。
leakcanary-object-watcher-android/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher"
>
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
</application>
</manifest>
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/AppWatcherInstaller.kt
/**
* 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()
/**
* When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
* [LeakCanaryProcess] automatically sets up the LeakCanary code
*/
internal class LeakCanaryProcess : AppWatcherInstaller() {
override fun onCreate(): Boolean {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(enabled = false)
return true
}
}
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
}
override fun query(
uri: Uri,
strings: Array<String>?,
s: String?,
strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(
uri: Uri,
contentValues: ContentValues?
): Uri? {
return null
}
override fun delete(
uri: Uri,
s: String?,
strings: Array<String>?
): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
}
我们可以看到,该ContentProvider除了在onCreate方法中进行了初始化处理,其他方法都是空实现。在上面的第26行,onCreate方法中初始化代码为InternalAppWatcher.install(application)。
InternalAppWatcher.install(Application) 方法完成了LeakCanary的初始化操作,里面涉及到协作的一些类的定义。
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/InternalAppWatcher.kt
internal object InternalAppWatcher {
// 通过判断lateinit修饰的Application是否已经初始化,来判断LeakCanary是否已经安装
val isInstalled
get() = ::application.isInitialized
// LeakCanary安装完成的回调,实际上对应的是leakcanary.internal.InternalLeakCanary这个类
private val onAppWatcherInstalled: (Application) -> Unit
val isDebuggableBuild by lazy {
(application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
}
lateinit var application: Application
// ObjectWatcher参数1
private val clock = object : Clock {
override fun uptimeMillis(): Long {
return SystemClock.uptimeMillis()
}
}
private val mainHandler = Handler(Looper.getMainLooper())
init {
// 这里为什么要这么费劲的获取该object的对象呢?
// 因为InternalLeakCanary类是上层模块的,这里没有办法直接引用
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
}
// 主线程抛出任务,delay 5s
private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { AppWatcher.config.enabled }
)
fun install(application: Application) {
...
checkMainThread()
// lateinit修饰的对象可以通过::<field>.isInitialized来判断有没有初始化
// 如果已经初始化了,则不需要再次install
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
// 安装ActivityDestroyWatcher、FragmentDestroyWatcher
val configProvider = { AppWatcher.config }
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
// 通知上层模块的InternalLeakCanary.invoke方法
onAppWatcherInstalled(application)
}
...
}
在上面的这个install方法中,干了三件事: 1. 初始化一个ObjectWatcher对象:其clock就是SystemClock.uptimeMillis(),checkRetainedExecutor是利用主线程Handler执行的任务 2. Activity、Fragment销毁的观察者的安装 3. 通知InternalLeakCanary,watcher已经安装完成。
由于Activity、Fragment的销毁不会立刻发生,所以我们先看看第3点中,InternalLeakCanary做了什么工作。
InternalLeakCanary的部分声明为object InternalLeakCanary : (Application) -> Unit,所以它到底实现了什么呢。这其实对应kotlin中Functions.kt文件的Function1接口,P1为Application,R为Unit即void:
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
所以接着看InternalLeakCanary.invoke方法即可,该方法也是完成了一些初始化操作,如下:
InternalLeakCanary.kt
override fun invoke(application: Application) {
this.application = application
// 内存泄漏时回调该类的方法
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
// gcTrigger使用的默认的
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
// HandlerThread + Handler 来处理后台任务
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
// 自定义的扩展方法来检测App是否可见(处于前台)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
// 动态添加Shortcut
addDynamicShortcut(application)
disableDumpHeapInInstrumentationTests()
}
其实上面这些初始化的变量,大部分我们都不会在检测内存泄漏时遇到,这些变量大部分都与dump heap有关。
这里展开说说其中的“自定义的扩展方法来检测App是否可见(处于前台)”这里面的实现原理,这个需求在日常开发中也会用到,也是通过向Application注册Activity生命周期回调,通过计算start-stop的Activity个数来实现的:
leakcanary-android-core/src/main/java/leakcanary/internal/VisibilityTracker.kt
internal class VisibilityTracker(
private val listener: (Boolean) -> Unit
) :
Application.ActivityLifecycleCallbacks by noOpDelegate() {
private var startedActivityCount = 0
/**
* Visible activities are any activity started but not stopped yet. An activity can be paused
* yet visible: this will happen when another activity shows on top with a transparent background
* and the activity behind won't get touch inputs but still need to render / animate.
*/
private var hasVisibleActivities: Boolean = false
override fun onActivityStarted(activity: Activity) {
startedActivityCount++
if (!hasVisibleActivities && startedActivityCount == 1) {
hasVisibleActivities = true
listener.invoke(true)
}
}
override fun onActivityStopped(activity: Activity) {
// This could happen if the callbacks were registered after some activities were already
// started. In that case we effectively considers those past activities as not visible.
if (startedActivityCount > 0) {
startedActivityCount--
}
if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
hasVisibleActivities = false
listener.invoke(false)
}
}
}
internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
registerActivityLifecycleCallbacks(VisibilityTracker(listener))
}
上面就是LeakCanary的初始化代码了,下一节开始说说Activity与Fragment是如何检测内存泄漏的。
4.2 如何观察Activity、Fragment
在上一节LeakCanary2初始化中,我们还没有讲解下面两行代码:
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
这两行代码都是通过向Application注册registerActivityLifecycleCallbacks从而获取每个启动的Activity,然后
- 对于Activity而言,直接观察Activity即可
- 对于Fragment而言,由于Fragment需要依附于Activity,且需要从Activity中获取FragmentManager,然后通过其registerFragmentLifecycleCallbacks方法观察Fragment
上面两行代码就是这个实现逻辑。 先看看Activity的观察逻辑:
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/ActivityDestroyWatcher.kt
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
// noOpDelegate()是一个动态代理的实现,不过里面没有写任何逻辑 所以是no op
// 此处相当于一个适配器方法
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(activity)
}
}
}
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
上面的逻辑很简单,就是对于每个Activity,在其onDestroy方法调用之后,调用objectWatcher.watch观察这个Activity。该方法的逻辑我们下一节再说。
然后我们看看Fragment里面的观察逻辑,由于Fragment有两种: 1. android.app.Fragment 2. support包里面的androidx.fragment.app.Fragment
前者的FragmentLifecycleCallbacks有API Level限制,限制为O;后者则没有API限制了,但是有androidx限制。所以FragmentDestroyWatcher会判断这两个的是否满足条件,满足条件后才会进行观察。
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/FragmentDestroyWatcher.kt
/**
* Internal class used to watch for fragments leaks.
*/
internal object FragmentDestroyWatcher {
private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
// 如果SDK大于等于O,则添加android.app.Fragment的观察者
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
// 通过反射判定androidx.fragment.app.Fragment以及其观察者是否存在
if (classAvailable(ANDROIDX_FRAGMENT_CLASS_NAME) &&
classAvailable(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
) {
// 反射实例化androidx Fragment的观察者并添加到里面list里面
val watcherConstructor = Class.forName(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
.getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
@kotlin.Suppress("UNCHECKED_CAST")
fragmentDestroyWatchers.add(
watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit
)
}
// 如果watcher为空,则不需要进行观察了
if (fragmentDestroyWatchers.size == 0) {
return
}
// 对每个Activity里面的所有的Fragment进行观察
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})
}
private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
true
} catch (e: ClassNotFoundException) {
false
}
}
}
AndroidOFragmentDestroyWatcher、AndroidXFragmentDestroyWatcher两者的源码非常类似,只是针对的Fragment不同而调用的API不同而已,下面以AndroidXFragmentDestroyWatcher为例看看里面是如何实现的。
leakcanary-object-watcher-android-androidx/src/main/java/leakcanary/internal/AndroidXFragmentDestroyWatcher.kt
internal class AndroidXFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(view)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(fragment)
}
}
}
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
}
实现就是向Activity的FragmentManager注册FragmentLifecycleCallbacks,这样在Fragment调用onDestroyView和onDestory之后就能观察Fragment的View或者Fragment本身了。
4.3 内存泄漏判定
现在我们来看看ObjectWatcher.watch(Any)方法,在上面一节中我们看到,Activity、Fragment的View、Fragment都是由该方法进行观察的,所以最后还是统一回到了这里。
leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt
/**
* Identical to [watch] with an empty string reference name.
*/
@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}
/**
* Watches the provided [watchedObject].
*
* @param name A logical identifier for the watched object.
*/
@Synchronized fun watch(
watchedObject: Any,
name: String
) {
if (!isEnabled()) {
return
}
// 将ReferenceQueue中出现的弱引用移除
// 这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
// 记下观测开始的时间
val watchUptimeMillis = clock.uptimeMillis()
// 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器
// 这样的话,弱引用被回收之前会出现在ReferenceQueue中
val reference =
KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (name.isNotEmpty()) " named $name" else "") +
" with key $key"
}
// 将key-reference保存到map中
watchedObjects[key] = reference
// 主线程5秒之后执行moveToRetained(key)方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
上面这段代码便是LeakCanary的关键代码之一: 1. 将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。 2. 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。
为什么会是5S,这里猜测与Android GC有关。在Activity.H中,收到GC_WHEN_IDLE消息时会进行Looper.myQueue().addIdleHandler(mGcIdler),而mGcIdler最后会触发doGcIfNeeded操作,在该方法中会判断上次GC与现在时间的差值,而这个值就是MIN_TIME_BETWEEN_GCS = 5*1000。
回到上面的代码,需要了解两个方法removeWeaklyReachableObjects()与moveToRetained(key)。前者比较简单,就会将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏。但是注意一下注释,这里强调了一点:弱引用入队列发生在终结函数或者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)
}
然后我们接着看重头戏moveToRetained方法:
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
5秒钟到了,还是先将引用队列中出现的对象从map中移除,因为它们没有内存泄漏。然后判断key还在不在map中,如果在的话,说明可能发生了内存泄漏。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis字段,然后通知所有的对象,内存泄漏发生了。
我们回忆一下,此处的onObjectRetainedListeners只有一个,就是我们在Activity、Fragment的观测者安装完毕后,通知了InternalLeakCanary,而InternalLeakCanary添加了一个监听器,就是它自己。所以我们看看InternalLeakCanary.onObjectRetained()方法:
leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
跟踪一下HeapDumpTrigger.onObjectRetained()方法:
leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
fun onObjectRetained() {
scheduleRetainedObjectCheck("found new object retained")
}
private fun scheduleRetainedObjectCheck(reason: String) {
if (checkScheduled) {
SharkLog.d { "Already scheduled retained check, ignoring ($reason)" }
return
}
checkScheduled = true
backgroundHandler.post {
checkScheduled = false
checkRetainedObjects(reason)
}
}
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
SharkLog.d { "No checking for retained object: LeakCanary.Config.dumpHeap is false" }
return
}
SharkLog.d { "Checking retained object because $reason" }
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
showRetainedCountWithDebuggerAttached(retainedReferenceCount)
scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
SharkLog.d {
"Not checking for leaks while the debugger is attached, will retry in $WAIT_FOR_DEBUG_MILLIS ms"
}
return
}
SharkLog.d { "Found $retainedReferenceCount retained references, dumping the heap" }
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
dismissRetainedCountNotification()
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
showRetainedCountWithHeapDumpFailed(retainedReferenceCount)
return
}
lastDisplayedRetainedObjectCount = 0
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
上面的代码就是对于内存泄漏判定的代码了,首先进入onObjectRetained方法,该方法会调用scheduleRetainedObjectCheck方法。此方法也就是在后台线程中执行checkRetainedObjects方法来检查泄漏的对象: 1. 首先获取泄漏对象的个数,如果大于0,则GC一次之后再次获取 2. 如果此时泄漏对象的个数大于等于5个config.retainedVisibleThreshold,则继续执行下面的代码,准备dump heap 3. 如果config里面配置的“调试时不允许dump heap”为false(默认值)且正在调试,则20s之后再试 4. 否则可以开始dump heap:此时会先记下dump发生的时间,取消内存泄漏通知,dump heap,清除所有观测事件小于等于dump发生时间的对象(因为这些对象已经处理完毕了),最后运行HeapAnalyzerService开始分析heap。
第26行的代码是如何获取泄露对象的个数的呢?我们想一下,在前面的代码中,主线程5秒之后执行了一段检测的代码,在这里面将所有泄露的对象都记下了当时的时间,存在retainedUptimeMillis字段里面。那么我们遍历所有元素,统计一下该字段不为默认值(-1)的个数即可:
leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt
/**
* 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 }
}
第29行,如果有内存泄漏的话,会调用gcTrigger.runGc()方法,这里的gcTrigger我们提到过,是GcTrigger.Default:
leakcanary/leakcanary-object-watcher/src/main/java/leakcanary/GcTrigger.kt
/**
* 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()
}
}
}
在上面注释中提到,System.gc()并不会每次都会执行GC,Runtime.gc()更有可能执行GC。 执行一次GC操作之后,下面粗暴的等待100ms,这样有足够的时间可以让弱引用移动到合适的引用队列里面。这就是GcTrigger.Default所干的事情。
GCTrigger触发GC之后,再次判断一下发生内存泄漏的对象的个数,如果仍然还有,那么肯定是泄漏无疑了,实锤!! 随后调用checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)方法,判断泄漏对象的个数是否达到了阈值,如果达到了则直接dump heap;否则发出一个内存泄漏的通知。
我们看一下这个方法:
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int
): Boolean {
// lastDisplayedRetainedObjectCount默认值为0,此处我们肯定有内存泄漏,因此countChanged为true
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
// 保存下当前的内存泄漏对象的个数
lastDisplayedRetainedObjectCount = retainedKeysCount
// 如果内存泄漏个数为0,则说明已经处理了所有的内存泄漏
if (retainedKeysCount == 0) {
SharkLog.d { "No retained objects" }
if (countChanged) {
showNoMoreRetainedObjectNotification()
}
return true
}
// 如果泄漏个数小于5个
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
SharkLog.d {
"Found $retainedKeysCount retained objects, which is less than the visible threshold of $retainedVisibleThreshold"
}
// 展示一个内存泄漏发生的通知
showRetainedCountBelowThresholdNotification(retainedKeysCount, retainedVisibleThreshold)
// 2秒钟之后再次执行检查泄漏对象的方法,看看泄漏个数是否有变化
scheduleRetainedObjectCheck(
"Showing retained objects notification", WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
// 如果泄漏个数大于等于5个,返回false,则返回后checkRetainedObjects方法会继续执行
// 此时就会dump heap
return false
}
至此,我们已经知道了内存泄漏是如何判定的。
LeakCanary2往后就是如何生成hprof文件以及如何解析了
-
生成hprof可以使用android.os.Debug.dumpHprofData(String fileName)方法 LeakCanary2中对应代码在/leakcanary-android-core/src/main/java/leakcanary/internal/AndroidHeapDumper.kt +88
-
解析hprof文件,LeakCanary2使用是Shark