前言
日常开发中,我们可能会遇到一些奇怪的问题和OOM,然后经过排查后发现是内存泄漏引起,那么我们如何避免内存泄漏呢?除了我们平时写代码多避免一些会导致内存泄漏的写法外,我们还可以用LeakCanary,它能够让我们在开发阶段去发现内存泄漏,进而避免把可能发生的内存泄漏带到线上去,同时它也是一个值得学习的框架。下面我们一起深呼一口气,通过源码学习它是如何对内存泄漏的监测与分析。
正文
在学习它的原理之前,我们先了解两个问题:
1.对象是在什么条件下被回收的?
简单的理解就是一个对象不再被使用了,则可以对其进行回收。判断一个对象是否可以被回收,目前有两种不同的机制,第一种是通过判断该对象的被引用次数,如果引用次数为0,则可以回收,但这样会存在的问题是如果两个对象都不再被使用了,但它们互相引用着,导致彼此的引用数都不为0,不会被回收。另一种方式是可达性分析,即从GCRoot开始,判断对象是否由root可达,不可达则可以进行回收,这样就可以避免上面说等情况,也是目前用得比较多的机制。GCRoot可以是一些应用级的变量或常量等。
2.什么是内存泄露?
当一个对象不再被使用了,但却没办法被回收的情况,例如一个Activity被销毁了,但它被其他引用持有着,导致虚拟机没办法对其进行回收。内存泄露会导致一些隐患,而且加大了发生OOM的概率。
LeakCanary的大致原理是通过监听特定对象的某个生命周期回调,然后将其创建一个弱引用并且与队列关联,5s后触发GC、再从队列检查是否有该弱引用(有表示对象被回收),如果弱引用不在队列里,则可能发生内存泄漏了,需进一步分析。
1.安装
加载该框架很简单,直接依赖即可:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
debugImplementation是因为我们只需在开发阶段使用。那么为什么直接依赖后,无需代码调用呢?
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}
相信有的读者已经猜到了使用ContentProvider,没错,它就是注册了AppWatcherInstaller,然后在应用启动的时候,去执行了 AppWatcher.manualInstall(application),进而对整个SDK的启动。
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
//1.
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
//2.
LeakCanaryDelegate.loadLeakCanary(application)
//3.
watchersToInstall.forEach {
it.install()
}
}
1.watchersToInstall是默认赋值的参数,是内置的一些watcher。
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
从命名可以大概猜测到是对Activity、Fragment、ViewModel、View、Serivce对象的观察。
2.主要是拿到一个listener,当有泄漏的时候,会进行回调,后面会讲到。
3.分别对各个watcher进行install,其实也是开始对各种要观察的对象进行一个监听和监测
2.监测
2.1 ActivityWatcher
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
代码比较简单,通过ActivityLifecycleCallbacks,监听所有Activity的onDestroy,当activity的onDestroy调用之后,便会通过reachabilityWatcher开始监测是否内存泄漏,reachabilityWatcher后面会统一讲到。
2.2 FragmentAndViewModelWatcher
class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
可以看到,一样是监听Activity的生命周期,不过这次是onActivityCreated,也就是在Activity的onCreate生命周期中,去监听这个Acitivty中的Fragment。我们看看fragmentDestroyWatchers是怎么获取的:
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
//1.
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
//2.
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
//3.
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
- 如果版本大于等于O(26),则添加AndroidOFragmentDestroyWatcher
- 检查是否支持AndroidX(通过反射androidx.fragment.app.Fragment看是否能给成功),是的话添加AndroidXFragmentDestroyWatcher
- 跟第二点一样,也是通过反射来看是否支持(android.support.v4.app.Fragment),是的话则反射获取AndroidSupportFragmentDestroyWatcher。
AndroidOFragmentDestroyWatcher跟AndroidSupportFragmentDestroyWatcher差不多,分别是通过fragmentManager和supportFragmentManager监听Fragment的onDestroyView和onDestroy。 我们重点看一下AndroidXFragmentDestroyWatcher,它还包含了对viewModel的监测:
internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
//2.
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null) {
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
//1.
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
}
- 可以看到,除了注册监听Fragment的生命周期以外,还出现了ViewModelClearedWatcher,用于监听属于Activity级别的ViewModel。
- 同时在Fragment的onCreate中,会通过ViewModelClearedWatcher去监听Fragment级别的ViewModel。
2.3 ViewModelClearedWatcher:
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {
private val viewModelMap: Map<String, ViewModel>?
init {
//1.
viewModelMap = try {
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null
}
}
//2.
override fun onCleared() {
viewModelMap?.values?.forEach { viewModel ->
reachabilityWatcher.expectWeaklyReachable(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
companion object {
fun install(
storeOwner: ViewModelStoreOwner,
reachabilityWatcher: ReachabilityWatcher
) {
//3.
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)
}
}
}
从代码中可以看到在install中,以传进来的activity/fragment作为storeOwner,然后去创建一个ViewModel,在onCleared去检测storeOwner的其他ViewModel是否内存泄漏。
- 通过反射获取storeOwner的其他ViewModel(map)。
- 在ViewModel调用onClear时,检测storeOwner的其他ViewModel是否内存泄漏。
- 创建一个ViewModelClearedWatcher对象,并且传进去storeOwner跟reachabilityWatcher
2.4 RootViewWatcher
class RootViewWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val listener = OnRootViewAddedListener { rootView ->
//省略判断trackDetached的代码
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
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)
}
})
}
}
override fun install() {
Curtains.onRootViewsChangedListeners += listener
}
override fun uninstall() {
Curtains.onRootViewsChangedListeners -= listener
}
}
RootViewWatcher主要通过添加监听起到Curtains中,监听器是在windows rootview变化的时候进行回调,进而对rootView进行监听,在onViewDetachedFromWindow时对rootView开始进行内存泄漏的检测。 下边是对windowType的检测显示,分别是Dialog、Toast、ToolTip和未知类型。
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activities are already tracked by ActivityWatcher
is Activity -> false
is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
// Probably a DreamService
else -> true
}
}
// Android widgets keep detached popup window instances around.
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
2.5 ServiceWatcher
override fun install() {
//1.检查线程
checkMainThread()
check(uninstallActivityThreadHandlerCallback == null) {
"ServiceWatcher already installed"
}
check(uninstallActivityManager == null) {
"ServiceWatcher already installed"
}
try {
//2,替换callback
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
Handler.Callback { msg ->
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
activityThreadServices[key]?.let {
onServicePreDestroy(key, it)
}
}
mCallback?.handleMessage(msg) ?: false
}
}
//3.动态代理
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" }
}
}
ServiceWatcher代码比较多,成员变量是通过反射获取的,例如其中activityThreadServices是通过反射ActivityThread拿到它的mServices成员。然后主要进行一些消息跟生命周期的hook,我们先从install开始分析。
- 检查是否在主线程
- 替换Handler的Callback,用于收到STOP_SERVICE消息时,将service记录到servicesToBeDestroyed这个map中(注意是使用一个弱引用包起来)。在uninstall时,用uninstallActivityThreadHandlerCallback替换回来原来的callback。
- 通过动态代理,hook住ActivityManager,在调用serviceDoneExecuting方法时,做了一些额外的逻辑,即判断第一个参数是否能够在之前的servicesToBeDestroyed中获取到对应的service,有的话,则在onServiceDestroyed方法中开始监测service是否内存泄漏。最后在uninstall中,会通过uninstallActivityThreadHandlerCallback进行替换回原来的activityManager实例。
3.分析泄漏对象
每中对象的监测,都是通过ObjectWatcher的expectWeaklyReachable开始的:
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//1.移除
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//2.生成一个监测对象的弱引用
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
//放到map里
watchedObjects[key] = reference
//3.默认是5秒后执行此任务
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
我们可以看到expectWeaklyReachable代码不多,下面根据注释的点进行分析:
- 移除的是之前一些被回收的监测对象。
- 创建一个弱引用,与queue绑定,方便观察。当该对象被gc后,会将其包装的弱引用添加到该队列中。
- 5秒后执行真正的监测逻辑。
moveToRetained方法:
@Synchronized private fun moveToRetained(key: String) {
//1.
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//2.
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
- 移除的是之前一些被回收的监测对象。
- 通过key从map中查询是否还存在对应弱引用。
- 如果还存在,则代表可能存在内存泄漏了,通过对象内存泄漏到listener回调出去。
那么listener收到泄漏回调后,会做什么事情呢?我们回到最开始manualInstall方法里的那行代码:LeakCanaryDelegate.loadLeakCanary(application)
//LeakCanaryDelegate
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
可以看到是通过反射获取到InternalLeakCanary的实例,然后进行调用:
override fun invoke(application: Application) {
//省略部分代码
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
//省略部分代码设置各个变量,dump内存时需要用到
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
}
我们可以看到,其实就是将InternalLeakCanary添加到objectWatcher的监听列表里边。
InternalLeakCanary的onObjectRetained方法执行的是scheduleRetainedObjectCheck:
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
进一步跟进heapDumpTrigger的scheduleRetainedObjectCheck方法可以知道里边postDelay一个任务,执行了checkRetainedObjects方法:
private fun checkRetainedObjects() {
//省略部分代码,主要是用于处理不dump内存快照有异常持有的对象
var retainedReferenceCount = objectWatcher.retainedObjectCount
//1.如果有异常被持有的对象,触发gc
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//2.判断数量是否达到阀值,还有当前app的状态
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
//3.检查距离最后一次的时间
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"
//4.dump内存快照
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
dump内存之前,还需做一些操作:
- 如果异常被持有的对象数量大于0,则触发一下gc
- 判断当前异常数量是否达到阀值
- 判断当前距离上一次dump的时间,如果小于60S,则不进行dump
- 以上都检查通过后,开始进行dump
4. dump内存快照
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
//检查是否retry、还有发送通知
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
//清理掉在dump之前的异常对象
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
//开启HeapAnalyzerService进行内存分析
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
根据代码我们可以知道,dump的跟做是交给了heapDumper去做的,它是一个AndroidHeapDumper的对象。
- dumpHeap主要逻辑是创建一个prof文件,然后通过Debug.dumpHprofData生成内存快照。
- 如果失败的话,会检查是否去重试跟发送通知。
- 如果成功,会清理掉objectWatcher中dump之前的异常对象,然后开启HeapAnalyzerService进行内存分析。
5. 内存分析
内存分析是使用了shark模块,核心步骤是读取hprof文件,找到之前标记的泄漏对象,寻找GcRoot,然后将分析结果保存到数据库中,我们点击通知栏进行跳转后便可以看到对象数据的展示。
结语
到这里本文的分享就结束了,内容比较多,但好的代码值得花时间去学习,因为从中可以借鉴到一些技巧跟实现思路,并且我们可以参考去自研一个类似的内存泄漏监测工具。最后给每一位读到这里的读者一个赞,同时也希望读者能够通过点赞对我给予支持。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。