LeakCanary原理分析
簡介
使用MAT來分析內存問題,有一些門檻,會有一些難度,並且效率也不是很高,對於一個內存泄漏問題,可能要進行多次排查和對比才能找到問題原因。 爲了能夠簡單迅速的發現內存泄漏,Square公司基於MAT開源了LeakCanary
總結來說LeakCanary是一個基於MAT用來檢測內存泄漏的一個有效的簡單好用的工具。
不足
申請大容量內存導致的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏無法檢測等,需要我們用Mat。
使用
2.0之後只需要在gradle裏添加一個這個就可以了 2.4都是用kotlin寫的。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
其他的註冊install的工作都放到AppWatcherInstaller中了。
AppWatcherInstaller這個類上,它繼承自ContentProvider,四大組件之一。ContentProvider的特點就是不用顯示調用初始化,在執行完application的初始化後就會調用ContentProvider的onCreate()方法。正是利用這一點,LeakCanary把註冊寫在了這裏面,有系統自動調用完成,對開發者完全無感知。
這裏也可以看出我們瞭解四大組件源碼Android啓動流程的重要性。我們可以做很多優化的事情。
internal sealed class AppWatcherInstaller : ContentProvider() {
....省略代碼....
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
....省略代碼....
}
原理
何時檢測泄漏
首先我們要先了解LeakCanary-Android主要是檢測Activity和Fragment的內存泄漏。其他的對象的泄漏需要我們自己去
我們帶着問題先去看下他的註冊源碼。
跟蹤AppWatcherInstaller中註冊的代碼到AppWatcher
AppWatcher.manualInstall(application)
object AppWatcher {
.....省略代碼
/**
* [AppWatcher] is automatically installed in the main process on startup. You can
* disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:
*
* ```
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_watcher_auto_install">false</bool>
* </resources>
* ```
*
* If you disabled automatic install then you can call this method to install [AppWatcher].
*/
fun manualInstall(application: Application) {
InternalAppWatcher.install(application)
}
}
繼續追蹤代碼 InternalAppWatcher.install(application)到InternalAppWatcher
/**
* Note: this object must load fine in a JUnit environment
*/
internal object InternalAppWatcher {
...省略代嗎
fun install(application: Application) {
...省略代嗎
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
...省略代嗎
}
...省略代嗎
}
InternalAppWatcher中做了ActivityDestroyWatcher.install
FragmentDestroyWatcher.install
我們繼續追蹤到FragmentDestroyWatcher和ActivityDestroyWatcher的代碼細節。
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
//創建Activity生命週期監聽對象 監聽Activity destroy方法然後調用 objectWatcher
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
//判斷config中配置是否監聽activity泄漏
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
companion object {
//伴生對象中install方法中創建ActivityDestroyWatcher對象並註冊Activiy監聽傳入ObjectWatcher ,Config
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
Fragment 則要複雜一些FragmentDestroyWatcher中的install中在Activity啓動的onCreate監聽中通過Activity獲取FragmentManager,通過FragmentManager.registerFragmentLifecycleCallbacks註冊監聽,主要是在onFragmentViewDestroyed 和 onFragmentDestroyed方法中監聽 ,onFragmentViewDestroyed中主要是對fragment.view的泄漏檢測。
其中對AndroidO 以及 AndroidX和Android庫進行了適配:
AndroidOFragmentDestroyWatcher
AndroidXFragmentDestroyWatcher
AndroidSupportFragmentDestroyWatcher
此處限於篇幅不列出具體代碼要看的可以自己導入下leakCanary去具體看下代碼。
如何檢測泄漏
通過上面的分析我們發現Fragment和Activity都是通過監聽destroy方法然後,去執行ObjectWatcher的watch方法,那看來核心的檢測代碼就在裏邊了。那我們現在就去探索一下吧。
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
追蹤objectWatcher.watch方法。
追蹤過程中你可能需要了解一下強軟弱虛四種應用和
一些常見的內存泄漏問題及弱引用的應用
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true }
) {
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}
/**
* Watches the provided [watchedObject].
*
* @param description Describes why the object is watched.
*/
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//先清除一次防止重複添加(應該是這麼個作用因爲key每次都是隨機生成的)
removeWeaklyReachableObjects()
//生成每個被觀察對象的key
val key = UUID.randomUUID()
.toString()
//主要是在dump的時候使用計算一個對象被引用了多久
val watchUptimeMillis = clock.uptimeMillis()
//KeyWeakReference是WeakRefrence的子類,LeakCanary的原理就是如果對象被回收了的話
//會把引用對象放到 queue(ReferenceQueue<Any>) 生成一個對watchedObject的弱引用
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"
}
//把相應的refrece放到被觀察對象map中,watchedObjects可變map
watchedObjects[key] = reference
//然後通過Handler發送延遲五秒消息去判斷是否還有引用沒回收並dump,下邊會介紹checkRetainedExecutor對象的定義
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
//poll出queue中的被回收的對象移除watchedObjects中的相應弱引用對象
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)
}
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
//handler延遲五秒後執行run方法中的moveToRetained方法
@Synchronized private fun moveToRetained(key: String) {
//先去poll出queue中的被回收的對象移除watchedObjects中的相應弱引用對象 防止5秒過程中可能對象已經被回收了 下邊還要進行一次gc和dump
removeWeaklyReachableObjects()
//拿出當前watch對象的弱引用
val retainedRef = watchedObjects[key]
//【標記最後一步】若該對象若引用不爲空則繼續下一步爲null則認爲已被回收了不作處理
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//執行onObjectRetainedListeners中每個監聽的onObjectRetained方法
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
}
checkRetainedExecutor和對Activity,Fragment的檢測ObjectWatch
都是定義在InternalAppWatcher中的前邊有說到過這個類。
private val checkRetainedExecutor = Executor {
//AppWatcher.config.watchDurationMillis
//在AppWatcher的Config類中定義val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),默認五秒
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { true }
)
接着代碼中分析的最後一步(有【標記最後一步】標記的地方 handler的五秒信息後清除後引用對象不爲null),
我們直接跟蹤ObjectWatcher中的addOnObjectRetainedListener代碼
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
鼠標放到這個方法上邊 Command + 觸摸板點擊,我們會發現我們到了InternalLeakCanary的invoke方法中但是這個地方我們從前邊分析過來也沒發現那裏調用了
override fun invoke(application: Application) {
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
//創建一個後臺子線程的Handler,backgroundHandler這個後邊是用來出來
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
}
但是不要急他肯定在哪裏被調用了,我們看的時候都注意在主流程上邊,忽略了這個沒關係我們再回頭去看下到底哪裏使用了它,經過在前邊AppWatcherInstaller,AppWatcher,InternalAppWatcher
我們發現他是通過反射調用了
internal object InternalAppWatcher {
private val onAppWatcherInstalled: (Application) -> Unit
...省略代嗎
init {//我們創建InternalAppWatcherinit方法就會調用創建InternalLeakCanary對象並把對象賦值給onAppWatcherInstalled,onAppWatcherInstalled這個是個Lamda表達式 其實就是InternalLeakCanary中的invoke方法表達式
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
}
fun install(application: Application) {
...省略代嗎
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
//在install的時候通過onAppWatcherInstalled調用InternalLeakCanary invoke方法
onAppWatcherInstalled(application)
}
...省略代嗎
}
Kotlin中init代碼塊和構造方法以及伴生對象中代碼的調用時機及執行順序
好了經過上變得分寫我們已經知道InternalLeakCanary在哪裏創建並註冊對象未回收監聽addOnObjectRetainedListener。
接下來我們分析InternalLeakCanary實現了監聽方法後方法裏調用了什麼。
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
fun onDumpHeapReceived(forceDump: Boolean) {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onDumpHeapReceived(forceDump)
}
}
繼續跟蹤
//調用
heapDumpTrigger.onObjectRetained()
//onObjectRetained方法的實現
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
//在後臺子線程的handler中檢測未回收對象
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects(reason)
}, delayMillis)
}
然後在分析checkRetainedObjects:
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
//配置中是否要dump堆棧信息並通知
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
//目前還有多少未回收對象
var retainedReferenceCount = objectWatcher.retainedObjectCount
//如果還有未回收對象則在進行一次GC不一定能執行確保對象能回收的都回收了,然後再去賦值retainedReferenceCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//檢測retainedReferenceCount是否爲零或者小於retainedVisibleThreshold = 5如果小於則跳過下邊的檢測延遲 WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L 2秒 重新從scheduleRetainedObjectCheck重新開始檢測,一直循環,直到大於等於5個或者等於0個,爲了防止頻發回收堆造成卡頓。爲零的話就直接顯示通知。
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
//大於5個後,如果處於debug模式,會再等20秒,再次執行scheduleRetainedObjectCheck操作。防止debug模式會減慢回收
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
//距離上次dumpheap的事件小於WAIT_BETWEEN_HEAP_DUMPS_MILLIS =60_000L 1分鐘 則繼續去檢測未回收對象知道大於一分鐘
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堆棧信息
dumpHeap(retainedReferenceCount, retry = true)
}
總結一下就是:
1)如果未回收對象個數小於5,不做操作等待5秒再次進行檢查未回收的個數,一直循環,直到大於等於5個或者等於0個,爲了防止頻發回收堆造成卡頓。
2)大於5個後,如果處於debug模式,會再等20秒,再次執行scheduleRetainedObjectCheck檢測操作。防止debug模式會減慢回收
3)距離上次堆棧分析是否大於等於1分鐘,如果沒有超過一分鐘,也需要再次延遲(1分鐘-當前距離上次的時間)再次循環執行scheduleRetainedObjectCheck檢測操作
6、如果上面的條件都符合了,就可以開始進行堆棧的分析了
1)、獲取到內容文件 Debug.dumpHprofData(heapDumpFile.absolutePath)
2)、objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)該操作是去掉以前已經分析過的對象,也就是去除掉之前的沒有回收掉的對象,不在本次分析範圍內
3)、HeapAnalyzerService開啓IntentService服務進行分析 具體分析就不寫了,因爲還沒看懂
4)、把結果插入數據庫(泄漏區分了應用本身的內存泄漏和類庫的內存泄漏),並且發送通知
好了 到這裏LeakCanary的原理和源碼分析完畢