浅谈LeakCanary2源码(二)

简介: 浅谈LeakCanary2源码(二)

leakcanary-object-watcher-android/src/main/java/leakcanary/internal/FragmentDestroyWatcher.kt

    /**
     * Internal class used to watch for fragments leaks.
     */
    internal object FragmentDestroyWatcher {
      private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
      private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
        "leakcanary.internal.AndroidXFragmentDestroyWatcher"
      fun install(
        application: Application,
        objectWatcher: ObjectWatcher,
        configProvider: () -> AppWatcher.Config
      ) {
        val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
        // 如果SDK大于等于O,则添加android.app.Fragment的观察者
        if (SDK_INT >= O) {
          fragmentDestroyWatchers.add(
              AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
          )
        }
        // 通过反射判定androidx.fragment.app.Fragment以及其观察者是否存在
        if (classAvailable(ANDROIDX_FRAGMENT_CLASS_NAME) &&
            classAvailable(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
        ) {
          // 反射实例化androidx Fragment的观察者并添加到里面list里面
          val watcherConstructor = Class.forName(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
              .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
          @kotlin.Suppress("UNCHECKED_CAST")
          fragmentDestroyWatchers.add(
              watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit
          )
        }
        // 如果watcher为空,则不需要进行观察了
        if (fragmentDestroyWatchers.size == 0) {
          return
        }
        // 对每个Activity里面的所有的Fragment进行观察
        application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
          override fun onActivityCreated(
            activity: Activity,
            savedInstanceState: Bundle?
          ) {
            for (watcher in fragmentDestroyWatchers) {
              watcher(activity)
            }
          }
        })
      }
      private fun classAvailable(className: String): Boolean {
        return try {
          Class.forName(className)
          true
        } catch (e: ClassNotFoundException) {
          false
        }
      }
    }

    AndroidOFragmentDestroyWatcherAndroidXFragmentDestroyWatcher两者的源码非常类似,只是针对的Fragment不同而调用的API不同而已,下面以AndroidXFragmentDestroyWatcher为例看看里面是如何实现的。

    leakcanary-object-watcher-android-androidx/src/main/java/leakcanary/internal/AndroidXFragmentDestroyWatcher.kt

      internal class AndroidXFragmentDestroyWatcher(
        private val objectWatcher: ObjectWatcher,
        private val configProvider: () -> Config
      ) : (Activity) -> Unit {
        private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
          override fun onFragmentViewDestroyed(
            fm: FragmentManager,
            fragment: Fragment
          ) {
            val view = fragment.view
            if (view != null && configProvider().watchFragmentViews) {
              objectWatcher.watch(view)
            }
          }
          override fun onFragmentDestroyed(
            fm: FragmentManager,
            fragment: Fragment
          ) {
            if (configProvider().watchFragments) {
              objectWatcher.watch(fragment)
            }
          }
        }
        override fun invoke(activity: Activity) {
          if (activity is FragmentActivity) {
            val supportFragmentManager = activity.supportFragmentManager
            supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
          }
        }
      }

      实现就是向Activity的FragmentManager注册FragmentLifecycleCallbacks,这样在Fragment调用onDestroyViewonDestory之后就能观察Fragment的View或者Fragment本身了。

      内存泄漏判定

      现在我们来看看ObjectWatcher.watch(Any)方法,在上面一节中我们看到,Activity、Fragment的View、Fragment都是由该方法进行观察的,所以最后还是统一回到了这里。

      leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt

        /**
          * Identical to [watch] with an empty string reference name.
          */
        @Synchronized fun watch(watchedObject: Any) {
          watch(watchedObject, "")
        }
        /**
          * Watches the provided [watchedObject].
          *
          * @param name A logical identifier for the watched object.
          */
        @Synchronized fun watch(
          watchedObject: Any,
          name: String
        ) {
          if (!isEnabled()) {
            return
          }
          // 将ReferenceQueue中出现的弱引用移除
          // 这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
          removeWeaklyReachableObjects()
          val key = UUID.randomUUID()
              .toString()
          // 记下观测开始的时间
          val watchUptimeMillis = clock.uptimeMillis()
          // 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器
          // 这样的话,弱引用被回收之前会出现在ReferenceQueue中
          val reference =
            KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
          SharkLog.d {
              "Watching " +
                  (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
                  (if (name.isNotEmpty()) " named $name" else "") +
                  " with key $key"
          }
          // 将key-reference保存到map中
          watchedObjects[key] = reference
          // 主线程5秒之后执行moveToRetained(key)方法
          checkRetainedExecutor.execute {
            moveToRetained(key)
          }
        }

        上面这段代码便是LeakCanary的关键代码之一:1. 将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。2. 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。

        为什么会是5S,这里猜测与Android GC有关。在Activity.H中,收到GC_WHEN_IDLE消息时会进行Looper.myQueue().addIdleHandler(mGcIdler),而mGcIdler最后会触发doGcIfNeeded操作,在该方法中会判断上次GC与现在时间的差值,而这个值就是MIN_TIME_BETWEEN_GCS = 5*1000

        回到上面的代码,需要了解两个方法removeWeaklyReachableObjects()moveToRetained(key)。前者比较简单,就会将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏。但是注意一下注释,这里强调了一点:弱引用入队列发生在终结函数或者GC发生之前

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

          然后我们接着看重头戏moveToRetained方法:

            @Synchronized private fun moveToRetained(key: String) {
              removeWeaklyReachableObjects()
              val retainedRef = watchedObjects[key]
              if (retainedRef != null) {
                retainedRef.retainedUptimeMillis = clock.uptimeMillis()
                onObjectRetainedListeners.forEach { it.onObjectRetained() }
              }
            }

            5秒钟到了,还是先将引用队列中出现的对象从map中移除,因为它们没有内存泄漏。然后判断key还在不在map中,如果在的话,说明可能发生了内存泄漏。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis字段,然后通知所有的对象,内存泄漏发生了。

            我们回忆一下,此处的onObjectRetainedListeners只有一个,就是我们在Activity、Fragment的观测者安装完毕后,通知了InternalLeakCanary,而InternalLeakCanary添加了一个监听器,就是它自己。所以我们看看InternalLeakCanary.onObjectRetained()方法:

            leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt

              override fun onObjectRetained() {
                if (this::heapDumpTrigger.isInitialized) {
                  heapDumpTrigger.onObjectRetained()
                }
              }

              跟踪一下HeapDumpTrigger.onObjectRetained()方法:

              leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt

                fun onObjectRetained() {
                  scheduleRetainedObjectCheck("found new object retained")
                }
                private fun scheduleRetainedObjectCheck(reason: String) {
                  if (checkScheduled) {
                    SharkLog.d { "Already scheduled retained check, ignoring ($reason)" }
                    return
                  }
                  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
                  if (retainedReferenceCount > 0) {
                    gcTrigger.runGc()
                    retainedReferenceCount = objectWatcher.retainedObjectCount
                  }
                  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
                  if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
                    showRetainedCountWithDebuggerAttached(retainedReferenceCount)
                    scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
                    SharkLog.d {
                        "Not checking for leaks while the debugger is attached, will retry in $WAIT_FOR_DEBUG_MILLIS ms"
                    }
                    return
                  }
                  SharkLog.d { "Found $retainedReferenceCount retained references, dumping the heap" }
                  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
                  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
                  dismissRetainedCountNotification()
                  val heapDumpFile = heapDumper.dumpHeap()
                  if (heapDumpFile == null) {
                    SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
                    scheduleRetainedObjectCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
                    showRetainedCountWithHeapDumpFailed(retainedReferenceCount)
                    return
                  }
                  lastDisplayedRetainedObjectCount = 0
                  objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
                  HeapAnalyzerService.runAnalysis(application, heapDumpFile)
                }

                上面的代码就是对于内存泄漏判定的代码了,首先进入onObjectRetained方法,该方法会调用scheduleRetainedObjectCheck方法。此方法也就是在后台线程中执行checkRetainedObjects方法来检查泄漏的对象:1. 首先获取泄漏对象的个数,如果大于0,则GC一次之后再次获取 2. 如果此时泄漏对象的个数大于等于5个config.retainedVisibleThreshold,则继续执行下面的代码,准备dump heap 3. 如果config里面配置的“调试时不允许dump heap”为false(默认值)且正在调试,则20s之后再试 4. 否则可以开始dump heap:此时会先记下dump发生的时间,取消内存泄漏通知,dump heap,清除所有观测事件小于等于dump发生时间的对象(因为这些对象已经处理完毕了),最后运行HeapAnalyzerService开始分析heap。

                第26行的代码是如何获取泄露对象的个数的呢?我们想一下,在前面的代码中,主线程5秒之后执行了一段检测的代码,在这里面将所有泄露的对象都记下了当时的时间,存在retainedUptimeMillis字段里面。那么我们遍历所有元素,统计一下该字段不为默认值(-1)的个数即可:

                leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt

                  /**
                    * Returns the number of retained objects, ie the number of watched objects that aren't weakly
                    * reachable, and have been watched for long enough to be considered retained.
                    */
                  val retainedObjectCount: Int
                    @Synchronized get() {
                      removeWeaklyReachableObjects()
                      return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
                    }

                  第29行,如果有内存泄漏的话,会调用gcTrigger.runGc()方法,这里的gcTrigger我们提到过,是GcTrigger.Default

                  leakcanary/leakcanary-object-watcher/src/main/java/leakcanary/GcTrigger.kt

                    /**
                      * Default implementation of [GcTrigger].
                      */
                    object Default : GcTrigger {
                      override fun runGc() {
                        // Code taken from AOSP FinalizationTest:
                        // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
                        // java/lang/ref/FinalizationTester.java
                        // System.gc() does not garbage collect every time. Runtime.gc() is
                        // more likely to perform a gc.
                        Runtime.getRuntime()
                            .gc()
                        enqueueReferences()
                        System.runFinalization()
                      }
                      private fun enqueueReferences() {
                        // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
                        // references to the appropriate queues.
                        try {
                          Thread.sleep(100)
                        } catch (e: InterruptedException) {
                          throw AssertionError()
                        }
                      }
                    }

                    在上面注释中提到,System.gc()并不会每次都会执行GC,Runtime.gc()更有可能执行GC。
                    执行一次GC操作之后,下面粗暴的等待100ms,这样有足够的时间可以让弱引用移动到合适的引用队列里面。这就是
                    GcTrigger.Default所干的事情。

                    GCTrigger触发GC之后,再次判断一下发生内存泄漏的对象的个数,如果仍然还有,那么肯定是泄漏无疑了,实锤!!
                    随后调用
                    checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)方法,判断泄漏对象的个数是否达到了阈值,如果达到了则直接dump heap;否则发出一个内存泄漏的通知。
                    我们看一下这个方法:

                      private fun checkRetainedCount(
                        retainedKeysCount: Int,
                        retainedVisibleThreshold: Int
                      ): Boolean {
                        // lastDisplayedRetainedObjectCount默认值为0,此处我们肯定有内存泄漏,因此countChanged为true
                        val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
                        // 保存下当前的内存泄漏对象的个数
                        lastDisplayedRetainedObjectCount = retainedKeysCount
                        // 如果内存泄漏个数为0,则说明已经处理了所有的内存泄漏
                        if (retainedKeysCount == 0) {
                          SharkLog.d { "No retained objects" }
                          if (countChanged) {
                            showNoMoreRetainedObjectNotification()
                          }
                          return true
                        }
                        // 如果泄漏个数小于5个
                        if (retainedKeysCount < retainedVisibleThreshold) {
                          if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
                            SharkLog.d {
                                "Found $retainedKeysCount retained objects, which is less than the visible threshold of $retainedVisibleThreshold"
                            }
                            // 展示一个内存泄漏发生的通知
                            showRetainedCountBelowThresholdNotification(retainedKeysCount, retainedVisibleThreshold)
                            // 2秒钟之后再次执行检查泄漏对象的方法,看看泄漏个数是否有变化
                            scheduleRetainedObjectCheck(
                                "Showing retained objects notification", WAIT_FOR_OBJECT_THRESHOLD_MILLIS
                            )
                            return true
                          }
                        }
                        // 如果泄漏个数大于等于5个,返回false,则返回后checkRetainedObjects方法会继续执行
                        // 此时就会dump heap
                        return false
                      }

                      至此,我们已经知道了内存泄漏是如何判定的,正如第3节开头所述:

                      LeakCanary 2.0 beta 3 检测内存泄漏原理
                      在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
                      随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。
                      这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用
                      Runtime.getRuntime().gc()进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。

                      LeakCanary2往后就是如何生成hprof文件以及如何解析了

                      相关文章
                      |
                      4月前
                      |
                      存储 监控 算法
                      LeakCanary 的内存泄露问题排查
                      LeakCanary 的内存泄露问题排查
                      68 0
                      |
                      存储 Java API
                      浅谈LeakCanary2源码(一)
                      浅谈LeakCanary2源码
                      55 0
                      |
                      Java Android开发
                      Android体系课之--LeakCanary内存泄露检测原理解析
                      #### 内存泄露 不需要的对象实例,无法被垃圾回收,比如被静态片段保留,就说可能发生内存泄露 ##### 常见场景: - 1.不清楚fragment视图的字段的情况下,将fragment添加到backstack中 - 2.Activity以context的形式被添加到一些类中,比如静态类,则gc无法清除,如Activity被非静态内部类Handler引用 - 3.注册一个监听器,广播接收器或者RxJava订阅时,引用了一个生命周期的对象,生命周期结束后,没有取消注册
                      |
                      iOS开发
                      afnetworking 内存泄漏问题
                      afnetworking 内存泄漏问题
                      368 0
                      afnetworking 内存泄漏问题
                      |
                      Android开发
                      在安卓项目中使用 Leakcanary 内存泄露检测工具
                      使用 (一)导入 导入 Leakcanary-watcher、Leakcanary-analyzer、Leakcanary-android, 在当前项目的引用 Leakcanary-android 这个 library。
                      249 0
                      |
                      消息中间件 存储 Java
                      LeakCanary 源码解析
                      LeakCanary 是由 Square 开源的针对 Android 和 Java 的内存泄漏检测工具。
                      Drools 6.4.0Final版本KieScanner内存泄漏Bug
                      Drools 6.4.0Final版本KieScanner内存泄漏Bug
                      334 0
                      Drools 6.4.0Final版本KieScanner内存泄漏Bug
                      |
                      Java Android开发 开发者
                      全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !
                      全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !
                      |
                      Java Android开发
                      【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 )
                      【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 )
                      156 0
                      |
                      Java
                      LeakCanary原理解析
                      简介 LeakCanary是一款开源的内存泄漏检查工具,在项目中,可以使用它来检测Activity是否能够被GC及时回收。github的地址为https://github.com/square/leakcanary 使用方式解析 将LeakCanary引入AS,在Application中调用如下方法,可以跟踪Activity是否被GC回收。
                      1207 0