LeakCanary源码分析

LeakCanary源码解析

内存泄露

今天来讲解一下老生常谈的问题了,内存泄露以及讲解LeakCanary是如果检测内存泄露的。

大家都在讲内存泄露,那么内存泄露的最根本的原因是什么?**最根本的原因就是该回收的对象没有被即使回收掉,导致了内存泄露。**要理解这句话,就要对java的垃圾回收机制有一定的了解了。什么是垃圾回收呢?就是java虚拟机在运行的时候会触发垃圾回收的机制,将那些没有用的,占用内存的对象回收掉。java虚拟机是怎么判断这个对象有没有用呢?是根据GC ROOT的可达性算法去判断的。就是如果一个对象,通过GC ROOT可达,那么它就是有用的对象。否则他就是没用的对象。

引用

无论是哪种方法,判断对象的存活都与“引用”有关。那么现在虚拟机对引用的期望是:当内存空间足够的时候,能保存在内存中;如果内存空间在进行垃圾回收之后还是非常紧张,则可以抛弃这些对象。所以就产生了四种引用

1.强引用:通过关键字new出来的对象,只要强引用还在,那么垃圾回收器就不会回收被引用的对象。

2.软引用:在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收。

3.弱引用:被弱引用引用的对象只能存活到下一垃圾回收发生之前。

4.虚引用:一个对象有无虚引用的存在,完全不影响其生存时间。也无法通过虚引用获取一个对象实例,为对象设置虚引用的目的就是能在这个对象被回收的时候,收到一个系统通知。

结合以上两点,可以得出,如果一个对象是被强引用,那么垃圾回收是不会回收这一部分对象的。那么同时如果虚拟机判断了GC ROOT不可达,那么又回收不了这个对象,那么就可以说这个对象造成了内存泄露。因为没有地方使用到这个对象了,而这个对象却还占用着的内存,所以他导致了内存的浪费,就是我们所说的内存泄露。

弱引用与引用队列
fun main() {
    val referenceQueue = ReferenceQueue<Pair<String, Int>?>()
    var pair: Pair<String, Int>? = Pair("小黄", 24)
    val weakReference = WeakReference(pair, referenceQueue)

    println(referenceQueue.poll()) //null

    pair = null

    System.gc()
    //GC 后休眠一段时间,等待 pair 被回收
    Thread.sleep(4000)

    println(referenceQueue.poll()) //java.lang.ref.WeakReference@d716361
}

可以看到,在 GC 过后 referenceQueue.poll() 的返回值变成了非 null,这是由于 WeakReferenceReferenceQueue 的一个组合特性导致的:在声明一个 WeakReference 对象时如果同时传入了 ReferenceQueue 作为构造参数的话,那么当 WeakReference 持有的对象被 GC 回收时,JVM 就会把这个弱引用存入与之关联的引用队列之中。依靠这个特性,我们就可以实现内存泄露的检测了

例如,当用户按返回键退出 Activity 时,正常情况下该 Activity 对象应该在不久后就被系统回收,我们可以监听 ActivityonDestroy 回调,在回调时把 Activity 对象保存到和 ReferenceQueue 关联的 WeakReference 中,在一段时间后(可以主动触发几次 GC)检测 ReferenceQueue 中是否有值,如果一直为 null 的话就说明发生了内存泄露。LeakCanary 就是通过这种方法来实现的。

LeakCanary源码分析

讲解了上面的一些前置知识,有助于大家更好的理解LeakCanary的工作原理。接下来就是讲解LeakCanary的工作流程和具体的细节了。这篇博客是基于2.8.1版本讲解的。

LeakCanary 2.8.1 版本中,LeakCanary 将初始过程交由 MainProcessAppWatcherInstaller 这个 ContentProvider 来自动完成

MainProcessAppWatcherInstaller#onCreate
//这是一个ContentProvider
internal class MainProcessAppWatcherInstaller : ContentProvider() {
    
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }

}

由于 ContentProvider 会在 Application 被创建之前就由系统调用其 onCreate() 方法来完成初始化,所以 LeakCanary 通过 AppWatcherInstaller 就可以拿到 Context 来完成初始化并随应用一起启动,通过这种方式就简化了使用者的引入成本。

MainProcessAppWatcherInstaller 最终会将 Application 对象传给 AppWatchermanualInstall(Application) 方法

AppWatcher#manualInstall
@JvmOverloads
fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),//这里也是一个默认的参数,5秒
    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()
    }
   //初始化一些配置,InternalLeakCanary类invoke方法,配置泄漏Listener,GC触发器,Dump类。
    LeakCanaryDelegate.loadLeakCanary(application)

    //调用各种install,其实就是为各种InstallableWatcher注册一些监听事件,这里有四个InstallableWatcher
    watchersToInstall.forEach {
        it.install()
    }
}
AppWatcher#appDefaultWatchers
fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

可以看出这里面有四中类型的InstallableWatcher。里面的实现大同小异

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)
  }
}

看一下ActivityWatcher就可以了,调用install方法,其实就是为了注册监听而已,看一下这个lifecycleCallbacks,可以看出,当Acrivity发生onDestory的时候,会回调到onActivityDestroyed这里面来。而这里面调用的是 reachabilityWatcher.expectWeaklyReachable。所以去看一下这个函数大概就知道LeakCanary到底是怎么工作的了。这之前,看一下reachabilityWatcher是什么东西,看会appDefaultWatchers函数可以知道,appDefaultWatchers其实就是objectWatcher

objectWatcher的初始化
val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
        check(isInstalled) {
            "AppWatcher not installed"
        }
        mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
)

所以expectWeaklyReachable其实就是ObjectWatcherexpectWeaklyReachable函数

ObjectWatcher#expectWeaklyReachable

这个函数我们需要一步一步慢慢看

  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    //首先去移除那些没有发生泄露的对象,第一次先忽略
    removeWeaklyReachableObjects()
    //生成一个key
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
   //通过这个key,构建一个弱引用对象,同时传入一个引用队列
    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
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)//这个方法会延迟5秒执行
    }
  }
ObjectWatcher#moveToRetained
@Synchronized private fun moveToRetained(key: String) {
    //移除那些没有发生泄露的对象
    removeWeaklyReachableObjects()
    //执行了移除操作后,在watchedObjects中还能找到对饮的弱引用的话,说明这个对象发生了泄露了
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
        retainedRef.retainedUptimeMillis = clock.uptimeMillis()///记录当前时间
        onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}
ObjectWatcher#removeWeaklyReachableObjects
private fun removeWeaklyReachableObjects() {
   //根据弱引用与引用队列那一节的分析,我们可以知道如果能在queue中的对象都是没有泄露的对象。这里的操作是遍历queue中的对象,然后根据它的key,将key对应的watchedObjects里面的对象移除。如果执行了该操作之后watchedObjects中还存在对应的key,那么说明发生GC的时候,这个对象没有被放入引用队列,也就是说该对象没有被回收,所以可以确定该对象造成了内存泄露
    var ref: KeyedWeakReference?
    do {
        ref = queue.poll() as KeyedWeakReference?
        if (ref != null) {
            watchedObjects.remove(ref.key)
        }
    } while (ref != null)
}

moveToRetained 方法就用于判断指定 key 关联的对象是否已经泄露,如果没有泄露则移除对该对象的弱引用,有泄露的话则更新其 retainedUptimeMillis 值,以此来标记其发生了泄露,并同时通过回调 onObjectRetained 来分析内存泄露链。

以上就是LeakCanary的关于如何检测内存泄露的源码分析了。其实检测内存泄露这一部分代码确实也比较简单,难的是获取内存泄露链和Dump那一部分。不过这两部分不在今天的讨论范围了,后续有机会的话,可以单独拿出来讲一下。好了今天的博客就这么多了,喜欢的小伙伴点个关注和赞吧。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值