本文基于LeakCanary 2.0源碼分析
LeakCanary - 官方地址
LeakCanary - GitHub代碼地址
LeakCanary 是什么
概念:LeakCanary是針對Android應(yīng)用的一個(gè)內(nèi)存泄漏監(jiān)控三方庫
能力:Activity、Fragment以及自主監(jiān)控的任何對象
出品:Square
LeakCanary 作用
基于對Android Framework層的認(rèn)知念祭,LeakCanary提供更精準(zhǔn)的泄漏原因分析能力霉咨,從而幫助開發(fā)者快速減少OOM Crash問題
LeakCanary 工作原理
預(yù)備知識
什么是內(nèi)存泄漏
在Java運(yùn)行環(huán)境下黄锤,內(nèi)存泄漏是指某個(gè)程序錯(cuò)誤導(dǎo)致應(yīng)用長時(shí)間一直保留某個(gè)不在需要的對象,以至于它不能被回收厘惦,而它是會(huì)占用內(nèi)存的疟丙,這就意味著內(nèi)存泄漏了嵌巷。持續(xù)累加奠滑,最終有可能導(dǎo)致發(fā)生內(nèi)存溢出問題丹皱。
例如一個(gè)Activity執(zhí)行完onDestroy方法后,它仍然被一個(gè)static變量強(qiáng)引用宋税,從而阻止了Activity被GC回收摊崭,導(dǎo)致Activity發(fā)生內(nèi)存泄漏
怎么判斷一個(gè)對象是否泄漏
從GC Roots出發(fā)進(jìn)行遍歷,強(qiáng)引用可到達(dá)對象杰赛,都是存活對象呢簸,不可達(dá)對象則為即將被回收的對象。如果那些存活對象本應(yīng)該是要被回收的乏屯,那么這個(gè)對象就是發(fā)生了內(nèi)存泄漏(見下圖根时,引用一張圖說明)
實(shí)際過程通常作法是針對核心對象Activity、Fragment進(jìn)行監(jiān)控分析
如何開始監(jiān)控
LeakCanary通過實(shí)現(xiàn)四大組件中的ContentProvider瓶珊,所以可以在App啟動(dòng)的時(shí)候執(zhí)行到LeakCanary的AppWatcherInstaller.onCreate方法啸箫,從而完成0侵入實(shí)現(xiàn)注冊監(jiān)控流程
監(jiān)控什么對象以及如何監(jiān)控到目標(biāo)對象
Activity: 通過注冊 Application.ActivityLifecycleCallbacks實(shí)現(xiàn)onActivityDestroyed執(zhí)行回調(diào),以監(jiān)控需要被回收的Activity是否被回收
Fragment: 通過注冊 FragmentManager.FragmentLifecycleCallbacks實(shí)現(xiàn)onFragmentViewDestroyed和onFragmentDestroyed回調(diào)伞芹,以監(jiān)控需要被回收的Fragment或者View是否被回收
如何確認(rèn)目標(biāo)對象泄漏
- 執(zhí)行回收:目標(biāo)對象如果在一個(gè)緩沖時(shí)間(5s)仍未被回收,我們通過手動(dòng)執(zhí)行GC蝉娜,然后在確認(rèn)其是否真的不能被回收
- 確認(rèn)是否回收:JVM中唱较,如果創(chuàng)建一個(gè)含有ReferenceQueue的WeakReference的A對象,這個(gè)WeakReference對應(yīng)的A對象如果被回收了召川,則A會(huì)被自動(dòng)加入到ReferenceQueue南缓,所以我們可以通過維護(hù)一個(gè)ReferenceQueue,通過創(chuàng)建目標(biāo)對象的含有ReferenceQueue的WeakReference荧呐,從而監(jiān)聽到目標(biāo)對象是否被回收
更多參考:Reference和ReferenceQueue深入解讀
如何分析目標(biāo)對象泄漏的原因
泄漏原因即尋找泄漏路徑
- 通過Debug.dumpHprofData汉形,dump一份hprof數(shù)據(jù)
- 讀取hprof數(shù)據(jù),整理出一份GcRoots對象索引
- 排序GcRoots對象倍阐,并構(gòu)造一份ReferencePathNode樹
- BFS遍歷概疆,找到泄漏對象的最短路徑節(jié)點(diǎn)
- 根據(jù)節(jié)點(diǎn),生成泄漏路徑
如何呈現(xiàn)目標(biāo)對象泄漏的原因
- 生成一個(gè)HeapDumpScreen峰搪,呈現(xiàn)泄漏信息
- 發(fā)出通知
LeakCanary 2.0與1.x版本對比
內(nèi)容 | 2.0 | 1.0 |
---|---|---|
語言 | kotlin | java |
使用 | 僅需引入庫岔冀,自動(dòng)注冊監(jiān)控 | 除引入庫,還需要手動(dòng)執(zhí)行install |
內(nèi)存分析 | shark概耻,基于Okio的自實(shí)現(xiàn)的輕巧內(nèi)存分析庫 | haha三方庫 |
其它 | fragment使套,支持 androidx | 無 |
LeakCanary 源碼分析
主要包的結(jié)構(gòu)介紹
主要工作的時(shí)序圖
相關(guān)源碼
注冊流程
AppWatcherInstaller.onCreate
// 繼承 ContentProvider
internal sealed class AppWatcherInstaller : ContentProvider() {
// App啟動(dòng) 執(zhí)行 onCreate
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
// 注冊啟動(dòng)(0 侵入)
InternalAppWatcher.install(application)
return true
}
}
InternalAppWatcher.install
fun install(application: Application) {
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
// Activity destroy方法監(jiān)控注冊
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
// Fragment destroy方法監(jiān)控注冊
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
ActivityDestroyWatcher.install
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
// 構(gòu)造 Application.ActivityLifecycleCallbacks
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
}
}
}
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
// 注冊 生命周期監(jiān)聽回調(diào)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
InternalLeakCanary.invoke
// 初始化相關(guān)類
override fun invoke(application: Application) {
this.application = application
// 添加保留對象監(jiān)聽
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// Android heap dumper類
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
// GC觸發(fā)器
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
// 相關(guān)線程 handler
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// Heap Dump 觸發(fā)器
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
...
}
監(jiān)控流程
以Activity.onDestory為例
ActivityDestroyWatcher.lifecycleCallbacks
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
// 開始監(jiān)控 銷毀的activity
objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
}
}
}```
ObjectWatcher.watch
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
// 移除已經(jīng)回收的監(jiān)聽對象
removeWeaklyReachableObjects()
// 隨機(jī)key
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 構(gòu)造KeyedWeakReference 用來監(jiān)聽目標(biāo)對象
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// 存儲 key + reference
watchedObjects[key] = reference
checkRetainedExecutor.execute {
// 執(zhí)行 沒有回收流程
moveToRetained(key)
}
}
ObjectWatcher.moveToRetained
@Synchronized private fun moveToRetained(key: String) {
// 再次移除被回收的對象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
// 如果沒有被回收 則開始執(zhí)行對象未被回收流程
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
HeapDumpTrigger
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
fun onObjectRetained() {
scheduleRetainedObjectCheck("found new object retained")
}
private fun scheduleRetainedObjectCheck(reason: String) {
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
// 如果還有未被回收的目標(biāo)對象罐呼,則出發(fā)GC,
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 觸發(fā)GC后侦高,對象仍未被回收嫉柴,開始dump
dumpHeap(retainedReferenceCount, retry = true)
}
HeapDumpTrigger.dumpHeap
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
// 儲存Android 的資源id 及其對應(yīng)的name
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// dumpHeap 到file
val heapDumpFile = heapDumper.dumpHeap()
lastDisplayedRetainedObjectCount = 0
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 啟動(dòng)service 開始dump分析
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
AndroidHeapDumper.dumpHeap
override fun dumpHeap(): File? {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
...
// 調(diào)用Debug的dumpHprofData 到目標(biāo)文件
return Debug.dumpHprofData(heapDumpFile.absolutePath)
heapDumpFile
}
分析流程
HeapAnalyzerService.onHandleIntentInForeground
override fun onHandleIntentInForeground(intent: Intent?) {
// 獲取目標(biāo) heap dump file
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
// 構(gòu)造 heap分析器
val heapAnalyzer = HeapAnalyzer(this)
// ...
// 執(zhí)行分析流程
val heapAnalysis =
heapAnalyzer.analyze(
heapDumpFile,
config.referenceMatchers,
config.computeRetainedHeapSize,
config.objectInspectors,
if (config.useExperimentalLeakFinders) config.objectInspectors else listOf(
ObjectInspectors.KEYED_WEAK_REFERENCE
),
config.metatadaExtractor,
proguardMappingReader?.readProguardMapping()
)
// 回調(diào)分析完成
config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}
HeapAnalyzer.analyze
fun analyze(
heapDumpFile: File,
referenceMatchers: List<ReferenceMatcher> = emptyList(),
computeRetainedHeapSize: Boolean = false,
objectInspectors: List<ObjectInspector> = emptyList(),
leakFinders: List<ObjectInspector> = objectInspectors,
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null
): HeapAnalysis {
val analysisStartNanoTime = System.nanoTime()
try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
// 讀取 文件,然后執(zhí)行分析
Hprof.open(heapDumpFile)
.use { hprof ->
// Hprof -> graph 轉(zhuǎn)換過程 目標(biāo)獲取GcRoots index
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
listener.onAnalysisProgress(EXTRACTING_METADATA)
// 獲取Android相關(guān) metadata (如sdk版本奉呛、收集廠商等信息)
val metadata = metadataExtractor.extractMetadata(graph)
val findLeakInput = FindLeakInput(
graph, leakFinders, referenceMatchers, computeRetainedHeapSize, objectInspectors
)
// 找泄漏最短路徑
val (applicationLeaks, libraryLeaks) = findLeakInput.findLeaks()
listener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 返回分析成功結(jié)果
return HeapAnalysisSuccess(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime), metadata,
applicationLeaks, libraryLeaks
)
}
}
HeapAnalyzer.findLeaks
private fun FindLeakInput.findLeaks(): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
// 找未被回收對象的 objectId
val leakingInstanceObjectIds = findRetainedObjects()
// 構(gòu)造pathFinder對象
val pathFinder = PathFinder(graph, listener, referenceMatchers)
val pathFindingResults =
// ?? 找泄漏對象到GcRoots的最短路徑
pathFinder.findPathsFromGcRoots(leakingInstanceObjectIds, computeRetainedHeapSize)
// 返回 泄漏路徑
return buildLeakTraces(pathFindingResults)
}
PathFinder.findPatchsFromGcRoots
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)
// 執(zhí)行 state计螺。findPathsFromGcRoots
return state.findPathsFromGcRoots()
}
private fun State.findPathsFromGcRoots(): PathFindingResults {
// GcRoots 生成節(jié)點(diǎn)隊(duì)列樹
enqueueGcRoots()
val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
visitingQueue@ while (queuesNotEmpty) {
val node = poll() // 循環(huán)取node
// 泄漏的節(jié)點(diǎn),則添加到shortestPathsToLeakingObjects 直到侧馅,全部找完
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
}
}
}
}
return PathFindingResults(shortestPathsToLeakingObjects, dominatedObjectIds)
}
呈現(xiàn)流程
DefaultOnHeapAnalyzedListener.onHeapAnalyzed
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
// 寫入 db
val (id, groupProjections) = LeaksDbHelper(application)
.writableDatabase.use { db ->
val id = HeapAnalysisTable.insert(db, heapAnalysis)
id to LeakTable.retrieveHeapDumpLeaks(db, id)
}
// 生成 泄漏信息到屏幕展示
val (contentTitle, screenToShow) = when (heapAnalysis) {
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
is HeapAnalysisSuccess -> {
var leakCount = 0
var newLeakCount = 0
var knownLeakCount = 0
var libraryLeakCount = 0
for ((_, projection) in groupProjections) {
leakCount += projection.leakCount
when {
projection.isLibraryLeak -> libraryLeakCount += projection.leakCount
projection.isNew -> newLeakCount += projection.leakCount
else -> knownLeakCount += projection.leakCount
}
}
application.getString(
R.string.leak_canary_analysis_success_notification, leakCount, newLeakCount,
knownLeakCount, libraryLeakCount
) to HeapDumpScreen(id)
}
}
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
val contentText = application.getString(R.string.leak_canary_notification_message)
// 構(gòu)建通知
Notifications.showNotification(
application, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_analysis_result,
LEAKCANARY_MAX
)
}