这里是船新版本的LeakCanary源码,是兄弟就砍我!(狗头)
1. 前言
1.1 LeakCanary是什么?
我们先来看看官网上对它的介绍:
LeakCanary is a memory leak detection library for Android
可见LeakCanary是适用于Android内存泄露检测库。
1.2 LeakCanary可以解决什么问题?
- 内存泄漏一直是开发人员比较头疼的问题,因为他并不是一个
Java异常,只有等到内存溢出的时候,我们才会去排查是否发生了内存泄漏的问题, - 当我们分析内存泄漏的时候,会使用
MAT去分析hprof文件,整个流程相对繁琐。
这个库的出现就是为了解决上面的问题,可以帮助开发人员大大减少OutOfMemoryError崩溃。
1.3 LeakCanary2的不同
LeakCanary2相对与LeakCanary发生了哪些变化呢?
1.4 LeakCanary的使用
我们先来看看之前的LeakCanary是怎么使用的:
- 首先先引入库:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
- 然后在自定义的
Application中调用方法:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
那么现在的LeakCanary2的使用:
dependencies {
// 使用debugImplementation是因为LeakCanary只能在debug版本使用。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'}
这样就可以了,实现的方式更简单了。
LeakCanary的组成
LeakCanary组成的模块:
leakcanary-android
leakcanary-android-core
leakcanary-object-watcher
leakcanary-object-watcher-android
leakcanary-android-instrumentation
leakcanary-object-watcher-android-support-fragments
leakcanary-object-watcher-android-androidx
leakcanary-deobfuscation-gradle-plugin
leakcanary-android-process
shark
shark-test
shark-log
shark-hprof
shark-hprof-test
shark-graph
shark-cli
shark-android
2. LeakCanary原理
2.1 LeakCanary初始化
我们发现LeakCanary的实现更简单了,是怎么回事呢?让我们看看源码,在 leakcanary-object-watcher-android模块中的AppWatcherInstaller.kt。
AppWatcherInstaller 是继承ContentProvider,ContentProvider的启动是要快于Application的加载的,所以重写了onCreate方法,在这里调用了install方法。
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application InternalAppWatcher.install(application)
return true
}
AppWatcherInstaller会有两个子类:
- MainProcess
在
app的进程中使用LeakCanary
我们可以在leakcanary-object-watcher-android模块中的AndroidManifest.xml中看到:
<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>
- LeakCanaryProcess
使用
leakcanary-android-process模块的时候,会在一个新的进程中去开启LeakCanary
在leakcanary-android-process模块中的 AndroidManifest.xml中:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary">
<application>
<service
android:name="leakcanary.internal.HeapAnalyzerService"
android:exported="false"
android:process=":leakcanary" />
<provider
android:name="leakcanary.internal.AppWatcherInstaller$LeakCanaryProcess"
android:authorities="${applicationId}.leakcanary-process.installer
android:process=":leakcanary"
android:exported="false"/>
</application>
</manifest>
可以看到这里启用了一个新的进程:leakcanary
需要注意的是,使用LeakCanaryProcess的时候,会关闭Watcher功能,因为在不同进程中,也无法观测App进程中的信息:
internal class LeakCanaryProcess : AppWatcherInstaller() {
override fun onCreate(): Boolean {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(enabled = false) return true
}
}
2.2 InternalAppWatcher#install()
我们现在进入到install()方法中去:
fun install(application: Application) {
SharkLog.logger = DefaultCanaryLog()
SharkLog.d { "Installing AppWatcher" }
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = {
AppWatcher.config
}
ActivityDestroyWatcher.install(application, objectWatcher,configProvider)
FragmentDestroyWatcher.install(application, objectWatcher,configProvider)
onAppWatcherInstalled(application)}
可以看到主要做了两件事情,就是对Activity和Fragment的注册观察,我们来对ActivityDestroyWatcher进行分析。
2.3 ActivityDestroyWatcher
进入到ActivityDestroyWatcher#install方法中:
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
我们在这里调用了registerActivityLifecycleCallbacks注册activity的生命周期回调。
PS: 在
Kotlin中,Application会有一个registerActivityLifecycleCallbacks这样的方法,当它被调用的时候,所有的Activity的生命周期都会被监控。
继续看下去:
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
在CallBack的回调onActivityDestroyed方法中,将activity加入到观察列表中。
我们进入到 watch 方法中看看:
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
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"
}
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
我们来看看removeWeaklyReachableObjects方法:
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
我们来看看 KeyedWeakReference是什么?
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
)
可以看到KeyedWeakReference 是继承 弱引用 WeakReference 的。
当GC发生的时候,弱引用都是会被回收掉的。
再看看看queue 是什么?
private val queue = ReferenceQueue<Any>()
实现了一个ReferenceQueue 的实例。
这个queue 就是用来存放被GC后的对象。在这里就是存放被GC过的activity对象。
再看看 watchedObjects :
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
就是用来保存弱引用的实例。
所以这个 removeWeaklyReachableObjects()方法就是将已经GC掉的对象从watchedObjects中移除掉。
继续看看 checkRetainedExecutor 是啥?
我们回到 InternalAppWatcher.kt中可以看到:
private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
---------
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
这里是使用了Handler来发送延迟消息,延迟5秒。
接着看看 moveToRetained 方法:
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
在moveToRetained方法中还是先删除一次GC掉的对象,其他没有被GC的对象会被认为保留并通知下去。
所以watch方法的流程:
- 首先先将已经
GC的对象移除出watchedObjects,也就是清理一次GC过的对象。 - 然后将需要观察的对象包装成
KeyedWeakReference,并放入到watchedObjects中。 - 然后通过
checkRetainedExecutor来调用moveToRetained通知。
2.4 onAppWatcherInstalled
在InternalAppWatcher.kt#install中的最后还会调用 onAppWatcherInstalled,这是一个方法对象,它的赋值是在init中:
init {
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
}
上面这一段代码的意思是 通过反射获取 InternalLeakCanary.INSTANCE 这个单例对象。
转化为Java代码:
void invoke(Application) {
}
因为InternalLeakCanary 是属于leakcanary-android-core 模块,这就是为什么要使用反射了。
所以调用onAppWatcherInstalled这个方法对象就是调用他的invoke方法:
override fun invoke(application: Application) {
this.application = application
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)
disableDumpHeapInTests()
}
调用 addOnObjectRetainedListener 来注册一个监听,也就是moveToRetained的回调监听。
AndroidHeapDumper则是通过用Debug.dumpHprofData()方法从虚拟机中 dump hprof 文件。
override fun dumpHeap(): File? {
//......
Debug.dumpHprofData(heapDumpFile.absolutePath)
//.....
}
--------------------------
public static void dumpHprofData(String fileName) throws IOException {
VMDebug.dumpHprofData(fileName);
}
GcTrigger通过调用Runtime.getRuntime().gc()方法触发虚拟机进行 GC 操作。
object Default : GcTrigger {
override fun runGc() {
Runtime.getRuntime() .gc()
enqueueReferences()
System.runFinalization()
}
HeapDumpTrigger 是用来管理Heap Dump的。
当onObjectRetained这个方法被回调的时候,触发Heap Dump的方法会被执行。
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
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects(reason)
}, delayMillis)
}
继续进入到 checkRetainedObjects 中:
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
// 这里是 保留的对象 也就是watch方法中保留的对象
var retainedReferenceCount = objectWatcher.retainedObjectCount
// 保留的对象不为0的时候
if (retainedReferenceCount > 0) {
// 触发一次gc
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 检测当前保留对象数量
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 默认 debug 时不执行,重新安排到 20s 后
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
// 60s内不执行,会重新安排一次
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 heap
dumpHeap(retainedReferenceCount, retry = true)
}
关键处都有注释,最终还是执行到了dumpHeap方法:
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
if (retry) {
scheduleRetainedObjectCheck(
reason = "failed to dump heap",
rescheduling = true,
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
return
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
可以看到先通过 heapDumper.dumpHeap() 来获取到heapDumpFile。
然后通过objectWatcher.clearObjectsWatchedBefore 来移除之前的保留对象:
@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
val weakRefsToRemove =
watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
weakRefsToRemove.values.forEach { it.clear() }
watchedObjects.keys.removeAll(weakRefsToRemove.keys)
}
最后调用HeapAnalyzerService.runAnalysis()进行分析。
所以ObjectWatcher来保存弱应用对象,HeapDumpTrigger 来触发heap dump,最后 HeapAnalyzerService来分析。
2.5 HeapAnalyzerService#runAnalysis
进入到runAnalysis方法:
fun runAnalysis(
context: Context,
heapDumpFile: File
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
startForegroundService(context, intent)
}
其实也是启动了一个前台服务,HeapAnalyzerService是继承 IntentService,调用的时候会走 onHandleIntent 方法,
override fun onHandleIntent(intent: Intent?) { onHandleIntentInForeground(intent)}
HeapAnalyzerService 会重写 onHandleIntentInForeground 这个方法:
override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}
这里最终进入到 analyzeHeap 这个方法中:
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
val heapAnalyzer = HeapAnalyzer(this)
val proguardMappingReader = try {
ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
} catch (e: IOException) {
null
}
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
objectInspectors = config.objectInspectors,
metadataExtractor = config.metadataExtractor,
proguardMapping = proguardMappingReader?.readProguardMapping()
)
}
可以看到heapAnalyzer.analyze这个方法,它是用来计算泄漏对象到GC Roots的最短路径。
进入到analyze方法中,可以看到如下操作:
return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
Hprof.open(heapDumpFile)
.use { hprof ->
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
}
} catch (exception: Throwable) {
HeapAnalysisFailure(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
HeapAnalysisException(exception)
)
}
可以看到使用了 Hprof.open来解析hprof文件,然后这个文件会被HprofHeapGraph.indexHprof调用,用来生成 heap 中的对象关系图。
fun indexHprof(
hprof: Hprof,
proguardMapping: ProguardMapping? = null,
indexedGcRootTypes: Set<KClass<out GcRoot>> = setOf(
JniGlobal::class,
JavaFrame::class,
JniLocal::class,
MonitorUsed::class,
NativeStack::class,
StickyClass::class,
ThreadBlock::class,
ThreadObject::class,
JniMonitor::class
)
): HeapGraph {
val index = HprofInMemoryIndex.createReadingHprof(hprof, proguardMapping, indexedGcRootTypes)
return HprofHeapGraph(hprof, index)
}
indexedGcRootTypes 是一个GC Roots的集合,包括:
JniGlobal::class,
native中的全局变量JavaFrame::class,
Java的局部变量JniLocal::class,
native中的局部变量MonitorUsed::class,
调用了
wait()或notify()方法 或者synchronized的对象NativeStack::class,
native中的入参和出参StickyClass::class,
系统类
ThreadBlock::class,
活动线程引用的对象
ThreadObject::class,
活动线程
JniMonitor::class
未知,可能是 native 中的同步对象
在生成graph之后,我们可以通过它来获取泄漏对象的ID。
在 FindLeakInput.analyzeGraph方法中:
val leakingObjectIds =
leakingObjectFinder.findLeakingObjectIds(graph)
然后生成泄漏对象报告
val (applicationLeaks, libraryLeaks) = findLeaks(leakingObjectIds)
leakcanary定义了两种泄漏类型
- ApplicationLeak
在
app应用中发现的泄漏
- LibraryLeak
应用依赖库中的代码泄漏
进入到findLeaks 中:
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
val pathFinder = PathFinder(graph, listener, referenceMatchers)
val pathFindingResults =
pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
SharkLog.d { "Found ${leakingObjectIds.size} retained objects" }
return buildLeakTraces(pathFindingResults)
}
我们继续进入到findPathsFromGcRoots中:
fun findPathsFromGcRoots(
leakingObjectIds: Set<Long>,
computeRetainedHeapSize: Boolean
): PathFindingResults {
listener.onAnalysisProgress(FINDING_PATHS_TO_RETAINED_OBJECTS)
val sizeOfObjectInstances = determineSizeOfObjectInstances(graph)
val state = State(leakingObjectIds, sizeOfObjectInstances, computeRetainedHeapSize)
return state.findPathsFromGcRoots()
}
这里是查询泄露对象到 GC Roots 的路径,我们进入到state.findPathsFromGcRoots()中:
private fun State.findPathsFromGcRoots(): PathFindingResults {
enqueueGcRoots()
// 省略部分代码
return PathFindingResults(shortestPathsToLeakingObjects,dominatedObjectIds)
}
先看看enqueueGcRoots 做了哪些操作
private fun State.enqueueGcRoots() {
// 将 GC Roots 进行排序
// 排序是为了确保 ThreadObject 在 JavaFrames 之前被访问,这样可以通过 ThreadObject.threadsBySerialNumber 获取它的线程信息
val gcRoots = sortedGcRoots()
val threadNames = mutableMapOf<HeapInstance, String>()
val threadsBySerialNumber = mutableMapOf<Int, Pair<HeapInstance, ThreadObject>>()
gcRoots.forEach { (objectRecord, gcRoot) ->
if (computeRetainedHeapSize) {
//计算泄漏对象保存的内存size
undominateWithSkips(gcRoot.id)
}
when (gcRoot) {
is ThreadObject -> {
// 保存活动线程的SerialNumber
threadsBySerialNumber[gcRoot.threadSerialNumber] = objectRecord.asInstance!! to gcRoot
// 加入队列
enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
is JavaFrame -> {
// Java局部变量
val threadPair = threadsBySerialNumber[gcRoot.threadSerialNumber]
if (threadPair == null) {
// Could not find the thread that this java frame is for.
enqueue(NormalRootNode(gcRoot.id, gcRoot))
} else {
val (threadInstance, threadRoot) = threadPair
val threadName = threadNames[threadInstance] ?: {
val name = threadInstance[Thread::class, "name"]?.value?.readAsJavaString() ?: ""
threadNames[threadInstance] = name
name
}()
// RefreshceMatchers 用于匹配已知的引用节点
// IgnoredReferenceMatcher 表示忽略这个引用节点
// LibraryLeakReferenceMatcher 表示这是库内存泄露对象
val referenceMatcher = threadNameReferenceMatchers[threadName]
if (referenceMatcher !is IgnoredReferenceMatcher) {
val rootNode = NormalRootNode(threadRoot.id, gcRoot)
val refFromParentType = LOCAL
// Unfortunately Android heap dumps do not include stack trace data, so
// JavaFrame.frameNumber is always -1 and we cannot know which method is causing the
// reference to be held.
val refFromParentName = ""
val childNode = if (referenceMatcher is LibraryLeakReferenceMatcher) {
LibraryLeakChildNode(
objectId = gcRoot.id,
parent = rootNode,
refFromParentType = refFromParentType,
refFromParentName = refFromParentName,
matcher = referenceMatcher
)
} else {
NormalNode(
objectId = gcRoot.id,
parent = rootNode,
refFromParentType = refFromParentType,
refFromParentName = refFromParentName
)
}
enqueue(childNode)
}
}
}
is JniGlobal -> {
// native 全局变量
val referenceMatcher = when (objectRecord) {
is HeapClass -> jniGlobalReferenceMatchers[objectRecord.name]
is HeapInstance -> jniGlobalReferenceMatchers[objectRecord.instanceClassName]
is HeapObjectArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
is HeapPrimitiveArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
}
if (referenceMatcher !is IgnoredReferenceMatcher) {
if (referenceMatcher is LibraryLeakReferenceMatcher) {
enqueue(LibraryLeakRootNode(gcRoot.id, gcRoot, referenceMatcher))
} else {
// 入列 NormalRootNode
enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
}
}
// 其他 GC Roots入列 NormalRootNode
else -> enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
}
}
这就是GC Roots节点入列的过程
在enqueue方法中,出现了两种队列toVisitLastQueue 和 优先级更高的toVisitQueue,泄漏优先级低的会先存入到toVisitLastQueue 中,有如下3种情况:
- LibraryLeakNode
- ThreadObject
- JavaFrame
这三种情况导致的泄漏比较少,所以放入到toVisitLastQueue中。
在将所有的 GC Roots节点入列后,使用广度优先遍历所有的节点,当访问节点是泄露节点,则添加到shortestPathsToLeakingObjects中。
val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
visitingQueue@ while (queuesNotEmpty) {
val node = poll()
if (checkSeen(node)) {
throw IllegalStateException(
"Node $node objectId=${node.objectId} should not be enqueued when already visited or enqueued"
)
}
if (node.objectId in leakingObjectIds) {
shortestPathsToLeakingObjects.add(node)
// Found all refs, stop searching (unless computing retained size)
if (shortestPathsToLeakingObjects.size == leakingObjectIds.size) {
if (computeRetainedHeapSize) {
listener.onAnalysisProgress(FINDING_DOMINATORS)
} else {
break@visitingQueue
}
}
}
when (val heapObject = graph.findObjectById(node.objectId)) {
// 将它的静态变量入列
is HeapClass -> visitClassRecord(heapObject, node)
// 将他的实例变量入列
is HeapInstance -> visitInstance(heapObject, node)
// 将非空元素入列
is HeapObjectArray -> visitObjectArray(heapObject, node)
}
}
这样,我们就将所有的泄漏对象都查找出来了。
我们回到findLeaks方法中,在找到泄漏节点之后就调用了buildLeakTraces方法:
private fun FindLeakInput.buildLeakTraces(pathFindingResults: PathFindingResults): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
// .....
val deduplicatedPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
//.....
return applicationLeaks to libraryLeaks
}
在通过findPathsFromGcRoots()获取的节点中,一个泄露对象可能会有多个引用路径,所以我们还需要做一次遍历,找到每个泄露对象的最短路径:
private fun deduplicateShortestPaths(inputPathResults: List<ReferencePathNode>): List<ReferencePathNode> {
val rootTrieNode = ParentNode(0)
for (pathNode in inputPathResults) {
val path = mutableListOf<Long>()
var leakNode: ReferencePathNode = pathNode
while (leakNode is ChildNode) {
path.add(0, leakNode.objectId)
leakNode = leakNode.parent
}
path.add(0, leakNode.objectId)
updateTrie(pathNode, path, 0, rootTrieNode)
}
val outputPathResults = mutableListOf<ReferencePathNode>()
findResultsInTrie(rootTrieNode, outputPathResults)
return outputPathResults
}
在这里会构建一个最短路径的树。在这里会生成泄漏对象路径ReferencePathNode,然后最终会生成LeakTrace。
然后会通过调用HeapAnalyzer.computeLeakStatuses()来计算路径上每个节点的泄露状态。
UI显示
在默认实现的DefaultOnHeapAnalyzedListener中,当前 hprof 文件分析成功后,会回调onHeapAnalyzed()方法:
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
// 入库
val id = LeaksDbHelper(application).writableDatabase.use { db ->
HeapAnalysisTable.insert(db, heapAnalysis)
}
val (contentTitle, screenToShow) = when (heapAnalysis) {
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
application.getString(
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV)
showToast(heapAnalysis)
printIntentInfo()
} else {
// 显示通知栏消息
showNotification(screenToShow, contentTitle)
}
}
点击通知栏的时候,唤起LeakActivity:
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
到这里就结束了,整个leakcanary的源码结构很清晰,值得学习,也值得反复学习。
参考:
LeakCanary2源码