前言
LeakCanary是Android面试中备受瞩目的一环,各大厂商如腾讯Matrix和快手Koom都自研内存泄漏检测框架,其原理分析也常被引述于帮助文档中。本文旨在抛却浮躁情绪,深入探究该框架的思想。
源码分析版本为 LeakCanary 2.12
一、LeakCanary的出现是为了解决什么问题?
在Android的开发领域,内存泄漏一直是一个备受关注的难题。这个问题通常可以分为两种主要情况:
- Java 内存泄露: 这是指那些已经不再使用的对象,却被生命周期更长的GC Root所引用,因此无法被垃圾回收机制识别为垃圾对象,他们会一直存在,从而产生内存泄漏。
- Native 内存泄露:这是由于Native内存没有像Java那样的垃圾回收机制,而未被手动回收,也会导致内存泄漏。
在这个背景下,LeakCanary作为Square公司开源的Java内存泄漏分析工具,专门用于在应用程序开发阶段,帮助开发者及时发现和解决Android应用中常见的内存泄漏问题。它通过监测对象引用关系,识别无法被垃圾回收的对象,提供详细的报告,帮助开发者精确定位内存泄漏的根本原因。这使得开发者能够更轻松地应对内存泄漏挑战,确保应用程序的性能和稳定性。
二、LeakCanary 如何实现内存泄漏监控?
LeakCanary 通过以下 2 点实现内存泄漏监控:
- Android Framework中的注册监听:LeakCanary会通过全局监听器或者Hook技术,注册对于Android Framework中重要对象(如Activity和Service)生命周期的监听。这意味着LeakCanary会追踪这些对象何时进入无用状态,比如Activity销毁后。
- 引用对象感知垃圾回收:LeakCanary会为这些对象创建弱引用,并设置一个延迟(默认为五秒)来观察这些弱引用是否如期进入Java虚拟机的引用队列。如果弱引用在延迟之后被回收,那么说明对象被正常释放,没有内存泄漏。但如果弱引用没有被回收,那么说明对象仍然被强引用持有,从而导致内存泄漏的发生。
三、LeakCanary 的初始化工作流程
在分析LeakCanary的引用包时我发现了,有一个包名含有watcher-android,推断这应该就是LeakCanary的入口了吧。
查看他的AndroidManifest.xml
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >
<uses-sdk android:minSdkVersion="14" />
<application>
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
</application>
</manifest>
分析这个清单文件我们可以得到以下几个信息
- package="com.squareup.leakcanary.objectwatcher": 这是应用程序的包名,指定了应用程序的唯一标识符。
- < uses-sdk android:minSdkVersion="14">: 这是使用SDK的声明,指定了应用程序支持的最低SDK版本为14。
- < application >: 这是应用程序组件的根元素,包含了应用程序的所有组件信息。
- < provider >: 这是一个应用程序提供者组件,用于在AndroidManifest.xml中声明一个自定义的服务。
- android:name="leakcanary.internal.MainProcessAppWatcherInstaller":这是服务的名称,指定了服务的唯一标识符。
- android:authorities="${applicationId}.leakcanary-installer": 这是服务的权限声明,指定了服务的授权方。
- android:enabled="@bool/leak_canary_watcher_auto_install": 这是服务的启用状态,指定了服务是否自动安装。
- android:exported="false": 这是服务的导出属性,指定了服务是否可以被其他应用程序访问。
进入MainProcessAppWatcherInstaller
代码语言:javascript复制internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
...
}
- 重写了ContentProvider的onCreate()方法。在该方法中,获取了应用程序的上下文**(applicationContext),并传给AppWatcher.manualInstall(application),**最后返回true表示创建成功。
进入初始化方法AppWatcher.manualInstall()
代码语言:javascript复制// 定义一个名为manualInstall的函数,该函数是LeakCanary库中的一个内部类AppWatcherInstaller的扩展函数
@JvmOverloads
fun manualInstall(
// 接收应用程序的上下文对象(Application)
application: Application,
// 默认值为5秒,表示在应用程序退出时保留LeakCanary观察器的时间(以毫秒为单位)
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
// 监视器列表
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
//确保当前线程是主线程
checkMainThread()
// 检查是否已经安装了LeakCanary观察器,如果已经安装,则抛出一个IllegalStateException异常
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
// 调用check()方法来检查retainedDelayMillis是否大于等于0,如果不是,则抛出一个异常
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
// 设置保留延迟时间
this.retainedDelayMillis = retainedDelayMillis
// 如果应用程序处于可调试构建状态,则调用LogcatSharkLog.install()方法进行日志记录
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
}
// 加载LeakCanaryDelegate并设置AppWatcher.objectWatcher
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install() // 安装传入的每个可安装的观察者(Watcher)
}
// 将installCause设置了一个RuntimeException异常
installCause = RuntimeException("manualInstall() first called here")
第9行: appDefaultWatchers(application) 这个方法实现添加多个监视器.
第38行: 为什么要写一个一定会执行的异常?
自定义一个installCause = RuntimeException("manualInstall() first called here")异常是为了在manualInstall()函数中提供一个默认的异常值。 在这个代码中,installCause变量被用于记录安装过程中发生的异常原因。如果installCause没有被显式地设置,那么它的默认值将是null。 通过将installCause设置为一个RuntimeException异常,我们可以确保在调用manualInstall()函数时,无论是否发生异常,都会执行这个异常。 这样做的好处是,当调用manualInstall()函数时,如果没有发生任何异常,那么installCause的值将为null,表示没有异常发生。而如果发生了异常,那么installCause将被赋值为一个RuntimeException异常对象,表示发生了异常。 通过这种方式,我们可以方便地跟踪和处理安装过程中可能发生的异常情况,以便更好地调试和解决问题。
第31行: 进入LeakCanaryDelegate.loadLeakCanary(application)
代码语言:javascript复制@Suppress("UNCHECKED_CAST") // 忽略未经检查的类型转换警告
val loadLeakCanary by lazy { // 懒加载单例对象
try {
// 通过反射获取leakcanary.internal.InternalLeakCanary类的INSTANCE字段
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit // 强制类型转换,将获取到的对象转换为(Application) -> Unit类型
} catch (ignored: Throwable) {
NoLeakCanary // 如果发生异常,进入NoLeakCanary执行空方法
}
}
object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}
override fun onObjectRetained() {
}
}
初始化 InternalLeakCanary ,因为 InternalLeakCanary 属于上层模块,无法直接调用到,所以使用了(反射)去创建。
使用反射的方式进行初始化,从而避免模块间的耦合。
当我们成功初始化InternalLeakCanary 后,就会执行里面的invoke方法,这里才是LeakCanary 启动初始化后的核心逻辑
代码语言:javascript复制override fun invoke(application: Application) {
_application = application
// 检查是否运行在Debuggable Build中,用于开发时检测内存泄漏
checkRunningInDebuggableBuild()
// 向AppWatcher的对象监视器添加一个监听器,用于监测对象的保留情况
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// 创建垃圾回收触发器
val gcTrigger = GcTrigger.Default
// 获取配置提供者
val configProvider = { LeakCanary.config }
// 创建处理线程
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// 创建HeapDumpTrigger实例,用于触发堆转储操作
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, configProvider
)
// 注册应用程序可见性监听器,用于在应用程序可见性更改时触发堆转储
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
// 注册应用程序已恢复的活动监听器
registerResumedActivityListener(application)
// 添加动态快捷方式到应用程序(金丝雀)
addDynamicShortcut(application)
// 在主线程上发布一个任务,使得日志在Application.onCreate()之后输出。
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// 在后台线程上发布一个任务,因为HeapDumpControl.iCanHasHeap()检查一个共享的偏好设置,
// 这会阻塞直到加载完成,从而导致StrictMode违规。
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
上面这些初始化的变量大部分都与dump heap有关 这里面有个面试问题,就是LeakCanary 如何感知应用是否处于前台,我们可以进入application.registerVisibilityListener一探究竟
代码语言:javascript复制internal class VisibilityTracker(
private val listener: (Boolean) -> Unit
) : Application.ActivityLifecycleCallbacks by noOpDelegate(), BroadcastReceiver() {
...
override fun onActivityStarted(activity: Activity) {
startedActivityCount
if (!hasVisibleActivities && startedActivityCount == 1) {
hasVisibleActivities = true
updateVisible()
}
}
override fun onActivityStopped(activity: Activity) {
if (startedActivityCount > 0) {
startedActivityCount--
}
if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
hasVisibleActivities = false
updateVisible()
}
}
...
这段代码我们可以看出他是通过ActivityLifecycleCallbacks计算start-stop的Activity个数来实现来判断应用是否在前台 至此基本该初始化的初始化,该注册的注册,下面研究如何检测内存泄漏
四、LeakCanary 如何检测内存泄漏
前面我们在看初始化的时候leakcanary.AppWatcher下的appDefaultWatchers注册了四个监视器用来执行各组件的监听,分别是:
- ActivityWatcher(application, reachabilityWatcher),
- FragmentAndViewModelWatcher(application, reachabilityWatcher),
- RootViewWatcher(reachabilityWatcher),
- ServiceWatcher(reachabilityWatcher)
ActivityWatcher
代码语言:javascript复制private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
通过监听ActivityLifecycleCallbacks的onActivityDestroyed并把对象通过reachabilityWatcher.expectWeaklyReachable处理
FragmentAndViewModelWatcher
监听 Fragment 和 ViewModel 的观察者
代码语言:javascript复制class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
// 定义一个列表来存储Fragment销毁时的回调函数
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O) {
// 添加Android O版本及以上的Fragment销毁回调函数
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
// 获取指定名称的Fragment销毁回调函数,并将其添加到列表中
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
// 获取指定名称的Support版本Fragment销毁回调函数,并将其添加到列表中
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
// 定义一个实现了Application.ActivityLifecycleCallbacks接口的对象,用于监听Activity的生命周期事件
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
// 遍历fragmentDestroyWatchers列表中的每个回调函数,并将当前Activity实例传递给它们
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
...
// 定义一个辅助方法,用于获取指定名称的Fragment和Watcher对象
private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
reachabilityWatcher: ReachabilityWatcher
): ((Activity) -> Unit)? {
return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)) {
// 尝试获取指定名称的Fragment和Watcher对象,并返回一个lambda表达式,该表达式接受一个Activity实例作为参数,并调用指定的Watcher对象的相应方法
val watcherConstructor =
Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
} else {
// 如果无法获取到指定的Fragment和Watcher对象,则返回null
null
}
}
// 定义一个辅助方法,用于检查指定的类是否可用
private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
true
} catch (e: Throwable) {
// 通常情况下,我们会期望能够加载指定的类。但是,在某些情况下(例如Android Support库的版本问题),我们可能会遇到ClassNotFoundException异常。在这种情况下,我们只能假设该类不可用,并返回false。
false
}
}
}
我们知道 FragmentManager 有三个版本:
- android.app.FragmentManager (Deprecated)
- android.support.v4.app.FragmentManager
- androidx.fragment.app.FragmentManager
在上面代码第10行-第35行就是判断该走哪一个FragmentManager, 随便找一个FragmentManager进入,以AndroidX举例
代码语言:javascript复制internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
// 定义一个对象 fragmentLifecycleCallbacks,继承自 FragmentManager.FragmentLifecycleCallbacks,用于监听 Fragment 的生命周期事件
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
// 当 Fragment 创建时调用
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
// 将当前 Fragment 和 reachabilityWatcher 传递给 ViewModelClearedWatcher 的 install 方法
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
// 当 Fragment 的视图被销毁时调用
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// 获取 Fragment 的视图
val view = fragment.view
// 如果视图不为空,检查其是否可以弱引用访问,以确保在 Fragment 销毁时清除对视图的引用,防止内存泄漏
if (view != null) {
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback "
"(references to its views should be cleared to prevent leaks)"
)
}
}
// 当 Fragment 销毁时调用
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// 检查 Fragment 是否可以弱引用访问,以确保在销毁时清除对其的引用,防止内存泄漏
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
// 重写 invoke 方法,接收一个 Activity 对象作为参数
override fun invoke(activity: Activity) {
// 如果传入的 Activity 是 FragmentActivity 类型,则获取其对应的 supportFragmentManager
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
// 使用 supportFragmentManager 注册 fragmentLifecycleCallbacks 对象,并设置为 true,表示在回调执行完毕后会自动移除
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
// 将 activity 和 reachabilityWatcher 传递给 ViewModelClearedWatcher 的 install 方法
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
}
与上面的ActivityWatcher一样,都是通过监听对应的生命周期在onFragmentViewDestroyed与onFragmentDestroyed来实现可达性追踪。 2.1 ViewModelClearedWatcher 在Fragment 创建的时候同时添加ViewModelClearedWatcher
代码语言:javascript复制internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner, // ViewModelStoreOwner 类型,用于获取 ViewModelStore
private val reachabilityWatcher: ReachabilityWatcher // ReachabilityWatcher 类型,用于检查 ViewModel 是否可以弱引用访问
) : ViewModel() {
// 获取 ViewModelStore 中的 mMap 字段,该字段是一个 Map,存储了所有的 ViewModel
private val viewModelMap: Map<String, ViewModel>? = try {
val storeClass = ViewModelStore::class.java
val mapField = try {
storeClass.getDeclaredField("map")
} catch (exception: NoSuchFieldException) {
// 如果找不到 mMap 字段,使用原来的 mMap 字段
storeClass.getDeclaredField("mMap")
}
mapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
null
}
// 当 ViewModel 被清除时,会调用此方法
override fun onCleared() {
viewModelMap?.values?.forEach { viewModel ->
// 检查每个 ViewModel 是否可以弱引用访问,如果不可以,打印日志
reachabilityWatcher.expectWeaklyReachable(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
companion object {
fun install(
storeOwner: ViewModelStoreOwner, // ViewModelStoreOwner 类型,用于获取 ViewModelStore
reachabilityWatcher: ReachabilityWatcher // ReachabilityWatcher 类型,用于检查 ViewModel 是否可以弱引用访问
) {
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
})
provider.get(ViewModelClearedWatcher::class.java)
}
}
}
通过继承ViewModel,在 onCleard() 方法执行时,通过反射拿到 ViewModelStore 中保存的 ViewModel数组 ,对每个 ViewModel 对象进行可达性追踪,从而判断是否存在内存泄漏。
RootViewWatcher
代码语言:javascript复制class RootViewWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
// 定义一个内部类 OnRootViewAddedListener,用于处理根视图添加事件
private val listener = OnRootViewAddedListener { rootView ->
// 根据根视图的类型(PHONE_WINDOW、POPUP_WINDOW、TOOLTIP、TOAST、UNKNOWN)判断是否需要追踪
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
// 如果根视图是Activity类型,则不追踪;如果是Dialog类型,则根据资源文件中的设置决定是否追踪;其他类型默认追踪
when (rootView.phoneWindow?.callback?.wrappedCallback) {
is Activity -> false
is Dialog -> {
// 使用应用上下文资源避免NotFoundException
val resources = rootView.context.applicationContext.resources
resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
}
else -> true
}
}
// Android小部件会保留分离的弹出窗口实例
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
// 如果需要追踪,则为根视图添加一个附加状态更改监听器
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
// 定义一个Runnable对象,用于在根视图从窗口分离时检查视图是否被追踪
val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
// 当根视图附加到窗口时,移除观察者
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
// 当根视图从窗口分离时,执行观察者
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
当前窗口类型 是 Dialog 、Tooltip 、Toast 或者 未知类型 时添加 View.OnAttachStateChangeListener 监听器,并初始化了一个 runable 用于执行view对象可达性追踪的回调,从而当这个View添加到窗口时,从Handler中移除该回调;在窗口移除时再添加到Handler中,从而触发View对象的可达性追踪。
ServiceWatcher
代码语言:javascript复制@SuppressLint("PrivateApi")
class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
// 存储需要被销毁的服务引用
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
// 获取ActivityThread类的实例
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
// 获取ActivityThread类的实例的方法
private val activityThreadInstance by lazy {
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
}
// 获取ActivityThread类的服务映射
private val activityThreadServices by lazy {
val mServicesField =
activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
// 定义回调函数变量
private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null
private var uninstallActivityManager: (() -> Unit)? = null
override fun install() {
// 检查是否已经在监听服务
checkMainThread()
check(uninstallActivityThreadHandlerCallback == null) {
"ServiceWatcher already installed"
}
check(uninstallActivityManager == null) {
"ServiceWatcher already installed"
}
try {
// 替换ActivityThread的Handler回调函数
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// 处理消息的回调函数
Handler.Callback { msg ->
// 防止崩溃,检查msg.obj是否为IBinder类型
if (msg.obj !is IBinder) {
return@Callback false
}
// 如果消息为STOP_SERVICE,则处理服务销毁
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
activityThreadServices[key]?.let {
onServicePreDestroy(key, it)
}
}
mCallback?.handleMessage(msg) ?: false
}
}
// 替换ActivityManager接口代理
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
onServiceDestroyed(token)
}
}
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
}
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services" }
}
}
override fun uninstall() {
// 检查是否在主线程
checkMainThread()
// 取消ActivityManager的代理
uninstallActivityManager?.invoke()
// 取消ActivityThread的Handler回调函数的安装
uninstallActivityThreadHandlerCallback?.invoke()
// 清空变量值
uninstallActivityManager = null
uninstallActivityThreadHandlerCallback = null
}
// 服务预销毁的回调函数
private fun onServicePreDestroy(
token: IBinder,
service: Service
) {
// 将服务引用添加到待销毁列表中
servicesToBeDestroyed[token] = WeakReference(service)
}
// 服务销毁的回调函数
private fun onServiceDestroyed(token: IBinder) {
// 从待销毁列表中移除服务引用,并通知reachabilityWatcher服务即将被销毁
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
// 替换ActivityThread的Handler回调函数的工具方法
private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
// ...省略部分代码...
}
// 替换ActivityManager接口代理的工具方法
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
// ...省略部分代码...
}
}
- ServiceWatcher 是一个用于监测 Android 服务销毁的模块。它通过反射获取了 ActivityThread 中的 Handler,并使用自定义的 Callback 替换了原来的 Callback,以实现监听服务停止的功能。当 Handler 收到消息的 what 字段为 STOP_SERVICE 时,表示服务即将停止,ServiceWatcher将该服务添加到追踪列表中。
- 此外,ServiceWatcher还通过动态代理方式代理了 IActivityManager 对象,以监测 IActivityManager 方法的调用。当监测到 serviceDoneExecuting() 方法被调用时,表示服务已真正结束,ServiceWatcher会从追踪列表中取出该服务,并进行可达性追踪,然后从列表中移除。
判定内存泄漏
前面我们把如何检测的过程看完了,现在到了关键点,如何判定。 在上述代码我们不难发现每个Watcher都有一个共同的执行代码,即 reachabilityWatcher.expectWeaklyReachable
代码语言:javascript复制@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
// 如果观察器被禁用,则直接返回
if (!isEnabled()) {
return
}
// 移除已经成为弱引用的对象
removeWeaklyReachableObjects()
// 生成一个随机的唯一标识符作为对象的键
val key = UUID.randomUUID().toString()
// 获取当前的系统启动时间
val watchUptimeMillis = clock.uptimeMillis()
// 创建一个KeyedWeakReference对象,用于观察被观察对象
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"
}
// 将KeyedWeakReference对象添加到被观察对象的映射中
watchedObjects[key] = reference
// 在后台线程执行将对象标记为已保留的操作
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
// 标记对象为已保留的操作
@Synchronized private fun moveToRetained(key: String) {
// 移除已经成为弱引用的对象
removeWeaklyReachableObjects()
// 获取指定键的弱引用对象
val retainedRef = watchedObjects[key]
// 如果找到了指定键的对象
if (retainedRef != null) {
// 更新对象的保留时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 触发对象被保留的监听器
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
// 移除已成为弱引用的对象
private fun removeWeaklyReachableObjects() {
// 弱引用对象会在对象变为弱引用可达状态时入队,但在最终化或垃圾回收之前
// 实际上并没有发生。因此,这里从队列中移除弱引用对象。
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// 从监测的对象集合中移除该对象
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
这段代码的核心功能是在启用了可达性追踪的情况下,监测并记录被追踪对象的生命周期。它通过弱引用和后台线程来实现这一功能,以便在一定时间后触发对象的保留操作,并通知监听器。同时,它也负责移除已经成为弱引用的对象,以保持集合的有效性。
5.1 初始化 KeyedWeakReference ,为什么要传入队列 queue ?
在初始化 KeyedWeakReference 时传入队列 queue 是为了在对象被垃圾回收时,能够将该对象放入指定的引用队列中。这是Java中一种常见的做法,用于实现对象的引用清理和跟踪。具体原因如下:
- 引用队列**(ReferenceQueue)**的作用:引用队列是用来跟踪对象是否被垃圾回收的工具。当一个对象的弱引用被垃圾回收器回收时,会将该弱引用添加到引用队列中,以便后续对其进行处理或记录。
- 监测对象是否被回收:在这段代码中, KeyedWeakReference 用于包装被监测的对象,而传入的 queue 就是一个引用队列。当被监测的对象被垃圾回收时,该对象的 KeyedWeakReference 引用将会被添加到 queue 中。
- 后续处理:通过将对象的引用放入引用队列,可以在后续的代码中检查引用队列,判断对象是否被回收。在 removeWeaklyReachableObjects() 方法中,就会从队列中移除已成为弱引用的对象,并从监测的对象集合中移除对应的条目。
5.2 onObjectRetainedListeners遍历执行了什么
代码语言:javascript复制// 重写 onObjectRetained() 方法,用于在对象被保留时执行操作
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
// 如果 heapDumpTrigger 已经初始化,则调用其 scheduleRetainedObjectCheck() 方法进行保留对象检查
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
// 获取当前计划检查的时间
val checkCurrentlyScheduledAt = checkScheduledAt
// 如果当前已经有计划进行检查,则直接返回
if (checkCurrentlyScheduledAt > 0) {
return
}
// 更新计划检查的时间为当前系统时间加上延迟时间
checkScheduledAt = SystemClock.uptimeMillis() delayMillis
// 使用 Handler 在延迟时间后执行检查操作
backgroundHandler.postDelayed({
// 将计划检查的时间重置为 0
checkScheduledAt = 0
// 调用 checkRetainedObjects() 方法进行保留对象检查
checkRetainedObjects()
}, delayMillis)
}
private fun checkRetainedObjects() {
// 判断当前应用是否具有堆权限
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
// 获取配置信息
val config = configProvider()
if (iCanHasHeap is Nope) {
// 如果无法进行堆检查,则执行以下操作
if (iCanHasHeap is NotifyingNope) {
// 在通知无法进行堆检查之前,先检查是否还有保留对象
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// 运行垃圾回收
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 获取无法进行堆检查的原因
val nopeReason = iCanHasHeap.reason()
// 判断是否需要进行堆检查
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"
)
}
- 首先进入onObjectRetained方法,该方法会调用scheduleRetainedObjectCheck方法。此方法也就是在后台线程中执行checkRetainedObjects方法来检查泄漏的对象:
- 首先获取泄漏对象的个数,如果大于0,则GC一次之后再次获取
- 如果此时泄漏对象的个数大于等于5个config.retainedVisibleThreshold,则继续执行下面的代码,准备**dump heap **
- 如果config里面配置的“调试时不允许dump heap”为false(默认值)且正在调试,则20s之后再试
- 否则可以开始dump heap:此时会先记下dump发生的时间,取消内存泄漏通知,dump heap,清除所有观测事件小于等于dump发生时间的对象(因为这些对象已经处理完毕了),最后运行HeapAnalyzer开始分析heap。
5.3 判断是否满足泄漏条件
代码语言:javascript复制private fun checkRetainedCount(
retainedKeysCount: Int, // 保留对象的数量
retainedVisibleThreshold: Int, // 保留对象的可见阈值
nopeReason: String? = null
): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount // 判断保留对象数量是否发生变化
lastDisplayedRetainedObjectCount = retainedKeysCount // 更新上次显示的保留对象数量
if (retainedKeysCount == 0) { // 如果保留对象数量为0
if (countChanged) { // 如果保留对象数量发生变化
SharkLog.d { "All retained objects have been garbage collected" } // 输出日志,表示所有保留对象已经被垃圾回收
onRetainInstanceListener.onEvent(NoMoreObjects) // 发送没有更多对象的通知
showNoMoreRetainedObjectNotification() // 显示没有更多保留对象的通知
}
return true // 返回true,表示需要进行堆检查
}
val applicationVisible = applicationVisible // 获取应用是否可见的状态
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod // 获取应用在不可见状态下的时间是否小于观察周期
if (countChanged) { // 如果保留对象数量发生变化
val whatsNext = if (applicationVisible) { // 如果应用可见
if (retainedKeysCount < retainedVisibleThreshold) { // 如果保留对象数量小于可见阈值
"not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)" // 输出日志,表示暂时不需要进行堆检查
} else {
if (nopeReason != null) { // 如果无法进行堆检查的原因不为空
"would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason" // 输出日志,表示现在可以进行堆检查,但是有无法进行堆检查的原因
} else {
"dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)" // 输出日志,表示现在可以进行堆检查
}
}
} else if (applicationInvisibleLessThanWatchPeriod) { // 如果应用不可见且在不可见状态下的时间小于观察周期
val wait =
AppWatcher.retainedDelayMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt) // 计算需要等待的时间
if (nopeReason != null) { // 如果无法进行堆检查的原因不为空
"would dump heap in $wait ms (app just became invisible) but $nopeReason" // 输出日志,表示现在可以进行堆检查,但是有无法进行堆检查的原因
} else {
"dumping heap in $wait ms (app just became invisible)" // 输出日志,表示现在可以进行堆检查
}
} else { // 如果应用不可见且在不可见状态下的时间大于等于观察周期
if (nopeReason != null) { // 如果无法进行堆检查的原因不为空
"would dump heap now (app is invisible) but $nopeReason" // 输出日志,表示现在可以进行堆检查,但是有无法进行堆检查的原因
} else {
"dumping heap now (app is invisible)" // 输出日志,表示现在可以进行堆检查
}
}
SharkLog.d {
val s = if (retainedKeysCount > 1) "s" else "" // 如果保留对象数量大于1,添加s后缀
"Found $retainedKeysCount object$s retained, $whatsNext" // 输出日志,显示保留对象数量和下一步操作
}
}
if (retainedKeysCount < retainedVisibleThreshold) { // 如果保留对象数量小于可见阈值
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) { // 如果应用可见或者应用在不可见状态下的时间小于观察周期
if (countChanged) { // 如果保留对象数量发生变化
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount)) // 发送低于阈值的事件
}
showRetainedCountNotification( // 显示保留计数通知
objectCount = retainedKeysCount, // 保留对象数量
contentText = application.getString(R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold) // 内容文本为可见阈值的字符串资源
)
scheduleRetainedObjectCheck(delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS) // 安排进行保留对象检查
return true // 返回true,表示进行检查
}
}
return false // 返回false,表示不需要进行检查
}
5.3.1 如何获取泄露对象的个数的呢?
代码语言:javascript复制/**
*返回保留对象的数量,即不可弱访问且已监视足够长的时间以被视为保留的监视对象的数量。
*/
val retainedObjectCount: Int
@Synchronized get() {
removeWeaklyReachableObjects()
return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
}
主线程5秒之后执行了一段检测的代码,在这里面将所有泄露的对象都记下了当时的时间,存在retainedUptimeMillis字段里面。那么我们遍历所有元素,统计一下该字段不为默认值(-1)的个数即可
5.3.2 GcTrigger如何执行
代码语言:javascript复制fun interface GcTrigger {
...
object Default : GcTrigger {
override fun runGc() {
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
通过调用 Runtime.getRuntime().gc() 来触发垃圾回收,然后调用 enqueueReferences() 方法将引用加入队列,最后调用 System.runFinalization() 来运行终结器。在 enqueueReferences() 方法中,使用 Thread.sleep(100) 来模拟延迟,以确保引用队列守护进程有足够的时间将引用移动到适当的队列中。如果线程被中断,会抛出 AssertionError。
五、LeakCanary 相关问题
为什么LeakCanary不能作为线上监控方案?
- 性能影响:LeakCanary 在进行内存泄漏检测时,需要触发垃圾回收(GC)。而 GC 过程中,线程会被暂停(STW),这将导致应用程序性能下降,甚至可能造成应用程序卡顿或测试伙伴过来告知有 bug。
- 生成报告:当 LeakCanary 检测到内存泄漏时,它需要生成内存快照(hprof 文件)以供进一步分析。然而,生成 hprof 文件会对系统性能产生额外的影响,并可能需要消耗较大的存储空间。
- 对应用程序的影响:LeakCanary 需要对应用程序进行修改以实现内存泄漏检测,这可能会对应用程序的正常运行造成一定的影响。
- 对生产环境的影响:在生产环境中使用 LeakCanary 时,如果其检测到内存泄漏,则可能会导致应用程序崩溃或无法正常运行,从而影响业务。
LeakCanary是如何自动安装的?
- LeakCanary 利用了 ContentProvider 的初始化机制来间接调用初始化 API。
- ContentProvider 的常规用法是提供内容服务,而另一个特殊的用法是提供无侵入的初始化机制。(代码上面已经有了就不重复写了,代码请看初始化流程)
LeakCanary 在哪个线程分析堆快照?
LeakCanary 已经成功生成 .hprof 堆快照文件,并且发送了一个 LeakCanary 内部事件 HeapDump,我们跟踪这个事件来到EventListener
代码语言:javascript复制val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
// 一个懒加载的委托事件监听器,根据InternalLeakCanary.formFactor的值来决定使用哪个事件监听器
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
// 根据不同的条件,为eventListeners列表添加不同的事件监听器
when {
//WorkerManager 多进程分析
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
RemoteWorkManagerHeapAnalyzer
//WorkManager 异步分析
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
//异步线程分析
else -> BackgroundThreadHeapAnalyzer
}
)
这三个代码分析我就不贴了,里面的执行逻辑差不多一样:
- 分析堆快照;
- 发送分析进度事件;
- 发送分析完成事件。
LeakCanary堆栈快照如何实现?
**AndroidDebugHeapAnalyzer.runAnalysisBlocking **方法来分析堆快照的,并在分析过程中和分析完成后发送回调事件。
代码语言:javascript复制fun runAnalysisBlocking(
heapDumped: HeapDump,
isCanceled: () -> Boolean = { false },
progressEventListener: (HeapAnalysisProgress) -> Unit
): HeapAnalysisDone<*> {
...
// 获取堆转储文件、持续时间和原因
val heapDumpFile = heapDumped.file
val heapDumpReason = heapDumped.reason
// 根据堆转储文件是否存在进行堆分析
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, progressListener, isCanceled)
} else {
missingFileFailure(heapDumpFile)
}
// 根据堆分析结果进行处理
val fullHeapAnalysis = when (heapAnalysis) {
...
}
// 更新进度,表示正在生成报告
progressListener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 在数据库中记录分析结果
val analysisDoneEvent = ScopedLeaksDb.writableDatabase(application) { db ->
val id = HeapAnalysisTable.insert(db, heapAnalysis)
when (fullHeapAnalysis) {
is HeapAnalysisSuccess -> {
val showIntent = LeakActivity.createSuccessIntent(application, id)
val leakSignatures = fullHeapAnalysis.allLeaks.map { it.signature }.toSet()
val leakSignatureStatuses = LeakTable.retrieveLeakReadStatuses(db, leakSignatures)
val unreadLeakSignatures = leakSignatureStatuses.filter { (_, read) ->
!read
}.keys.toSet()
HeapAnalysisSucceeded(
heapDumped.uniqueId,
fullHeapAnalysis,
unreadLeakSignatures,
showIntent
)
}
is HeapAnalysisFailure -> {
val showIntent = LeakActivity.createFailureIntent(application, id)
HeapAnalysisFailed(heapDumped.uniqueId, fullHeapAnalysis, showIntent)
}
}
}
// 触发堆分析完成的监听器
LeakCanary.config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
return analysisDoneEvent
}
核心分析代码在analyzeHeap(…)
代码语言:javascript复制private fun analyzeHeap(
heapDumpFile: File,
progressListener: OnAnalysisProgressListener,
isCanceled: () -> Boolean
): HeapAnalysis {
...
// Shark 堆快照分析器
val heapAnalyzer = HeapAnalyzer(progressListener)
...
// 构建对象图信息
val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile)
val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping())
...
// 开始分析
heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
graph = graph,
leakingObjectFinder = config.leakingObjectFinder, // 默认是 KeyedWeakReferenceFinder
referenceMatchers = config.referenceMatchers, // 默认是 AndroidReferenceMatchers
computeRetainedHeapSize = config.computeRetainedHeapSize, // 默认是 true
objectInspectors = config.objectInspectors, // 默认是 AndroidObjectInspectors
metadataExtractor = config.metadataExtractor // 默认是 AndroidMetadataExtractor
)
}
继续往下走进入heapAnalyzer.analyze
代码语言:javascript复制fun analyze(
heapDumpFile: File, // 堆内存转储文件
graph: HeapGraph, // 表示堆内存结构的HeapGraph对象
leakingObjectFinder: LeakingObjectFinder, // 用于查找泄漏对象的LeakingObjectFinder实例
referenceMatchers: List<ReferenceMatcher> = emptyList(), // 类和实例引用匹配器列表
computeRetainedHeapSize: Boolean = false, // 是否计算保留的堆大小
objectInspectors: List<ObjectInspector> = emptyList(), // 用于检查对象类型的ObjectInspector对象列表
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP // 用于提取元数据的MetadataExtractor实例
): HeapAnalysis { // 返回一个HeapAnalysis对象
val analysisStartNanoTime = System.nanoTime() // 记录分析开始时的纳秒时间
// 创建一个DelegatingObjectReferenceReader实例,负责读取类、实例和数组引用
val referenceReader = DelegatingObjectReferenceReader(
...
)
// 调用另一个重载版本的analyze函数,传入所有参数以及刚刚创建的referenceReader
return analyze(
heapDumpFile,
graph,
leakingObjectFinder,
referenceMatchers,
computeRetainedHeapSize,
objectInspectors,
metadataExtractor,
referenceReader
).run {
// 计算分析持续时间(以毫秒为单位),并根据结果的类型更新HeapAnalysis对象的analysisDurationMillis字段
val updatedDurationMillis = since(analysisStartNanoTime)
when (this) {
is HeapAnalysisSuccess -> copy(analysisDurationMillis = updatedDurationMillis)
is HeapAnalysisFailure -> copy(analysisDurationMillis = updatedDurationMillis)
}
}
}
继续往下进入analyzeGraph
代码语言:javascript复制private fun FindLeakInput.analyzeGraph(
metadataExtractor: MetadataExtractor, // 元数据提取器
leakingObjectFinder: LeakingObjectFinder, // 泄漏对象查找器
heapDumpFile: File, // 堆内存转储文件
analysisStartNanoTime: Long // 分析开始时的纳秒时间
): HeapAnalysisSuccess {
// 监听器进度更新
listener.onAnalysisProgress(EXTRACTING_METADATA)
// 从图中提取元数据
val metadata = metadataExtractor.extractMetadata(graph)
// 查找所有保留但未引用的弱引用实例
val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
.count { it.isRetained && !it.hasReferent }
// 如果存在保留但未引用的弱引用实例,将其计数添加到元数据中
// 这种情况很少发生,因为我们通常在堆转储之前删除所有已清除的弱引用
val metadataWithCount = if (retainedClearedWeakRefCount > 0) {
metadata ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances")
} else {
metadata
}
// 监听器进度更新
listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS)
// 查找泄漏对象的ID
val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
// 根据泄漏对象ID查找泄漏情况(应用程序泄漏、库泄漏和不可达对象)
val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
// 返回分析成功的结果,包括堆内存转储文件、创建时间、分析持续时间、元数据、泄漏情况等信息
return HeapAnalysisSuccess(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
metadata = metadataWithCount,
applicationLeaks = applicationLeaks,
libraryLeaks = libraryLeaks,
unreachableObjects = unreachableObjects
)
}
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
// 创建一个PathFinder实例,用于在图中查找从垃圾回收根节点到泄漏对象的路径
val pathFinder = PathFinder(graph, listener, referenceReader, referenceMatchers)
// 使用PathFinder实例查找从垃圾回收根节点到泄漏对象的路径
val pathFindingResults =
pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
// 查找不可达对象
val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)
// 对找到的最短路径进行去重处理
val shortestPaths =
deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
// 通过最短路径检查对象
val inspectedObjectsByPath = inspectObjects(shortestPaths)
// 如果找到了支配树,计算保留大小;否则返回null
val retainedSizes =
if (pathFindingResults.dominatorTree != null) {
computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree)
} else {
null
}
// 根据最短路径、检查过的对象和保留大小构建泄漏跟踪信息
val (applicationLeaks, libraryLeaks) = buildLeakTraces(
shortestPaths, inspectedObjectsByPath, retainedSizes
)
// 返回泄漏和不可达对象的结果
return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects)
}
可以看到,堆快照分析最终是交给 Shark 中的 HeapAnalizer 完成的,核心流程是:
- 在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象;
- 分析 KeyedWeakReference 对象的最短引用链,并按照引用链签名分组,按照 Application Leaks 和 Library Leaks 分类;
- 返回分析完成事件。
LeakCanary 如何筛选 ~~~ 怀疑对象?
在上述代码FindLeakInput.analyzeGraph(...)中,第30行进入FindLeakInput.findLeaks(...),再进入第60行**inspectObjects(shortestPaths)**中 FindLeakInput.inspectObjects
代码语言:javascript复制private fun FindLeakInput.inspectObjects(shortestPaths: List<ShortestPath>): List<List<InspectedObject>> {
...
return leakReportersByPath.map { leakReporters ->
computeLeakStatuses(leakReporters)
}
}
进入computeLeakStatuses(leakReporters)
代码语言:javascript复制private fun computeLeakStatuses(leakReporters: List<ObjectReporter>): List<InspectedObject> {
...
for ((index, reporter) in leakReporters.withIndex()) {
val resolvedStatusPair =
resolveStatus(reporter, leakingWins = index == lastElementIndex).let { statusPair ->
if (index == lastElementIndex) {
// The last element should always be leaking.
when (statusPair.first) {
LEAKING -> statusPair
UNKNOWN -> LEAKING to "This is the leaking object"
NOT_LEAKING -> LEAKING to "This is the leaking object. Conflicts with ${statusPair.second}"
}
} else statusPair
}
...
for (i in 0 until lastNotLeakingElementIndex) {
val (leakStatus, leakStatusReason) = leakStatuses[i]
val nextNotLeakingIndex = generateSequence(i 1) { index ->
if (index < lastNotLeakingElementIndex) index 1 else null
}.first { index ->
leakStatuses[index].first == NOT_LEAKING
}
// Element is forced to NOT_LEAKING
val nextNotLeakingName = simpleClassNames[nextNotLeakingIndex]
leakStatuses[i] = when (leakStatus) {
UNKNOWN -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking"
NOT_LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking and $leakStatusReason"
LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking. Conflicts with $leakStatusReason"
}
}
if (firstLeakingElementIndex < lastElementIndex - 1) {
// We already know the status of firstLeakingElementIndex and lastElementIndex
for (i in lastElementIndex - 1 downTo firstLeakingElementIndex 1) {
val (leakStatus, leakStatusReason) = leakStatuses[i]
val previousLeakingIndex = generateSequence(i - 1) { index ->
if (index > firstLeakingElementIndex) index - 1 else null
}.first { index ->
leakStatuses[index].first == LEAKING
}
// Element is forced to LEAKING
val previousLeakingName = simpleClassNames[previousLeakingIndex]
leakStatuses[i] = when (leakStatus) {
UNKNOWN -> LEAKING to "$previousLeakingName↑ is leaking"
LEAKING -> LEAKING to "$previousLeakingName↑ is leaking and $leakStatusReason"
NOT_LEAKING -> throw IllegalStateException("Should never happen")
}
}
}
在这里我们找到对象是否内存泄漏的三种状态,在可视化界面中我们我们便可得知界面是如何打印 DisplayLeakAdapter.kt
代码语言:javascript复制...
val reachabilityString = when (leakingStatus) {
UNKNOWN -> extra("UNKNOWN")
NOT_LEAKING -> "NO" extra(" (${leakingStatusReason})")
LEAKING -> "YES" extra(" (${leakingStatusReason})")
}
...
// 是否为怀疑对象
fun referencePathElementIsSuspect(index: Int): Boolean {
return when (referencePath[index].originObject.leakingStatus) {
UNKNOWN -> true
NOT_LEAKING -> index == referencePath.lastIndex || referencePath[index 1].originObject.leakingStatus != NOT_LEAKING
else -> false
}
}
六、LeakCanary 总结
至此基本上LeakCanary过了一遍,本人代码经验有限,有些代码解释不一定正确,还望多多包涵.
参考资料
- LeakCanary 官网
- LeakCanary Github 仓库
- Android 开源库 #7 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊! - 掘金