Android memory leak-LeakCanary source code principle analysis

LeakCanary principle analysis

Introduction

Using MAT to analyze memory problems has some thresholds, some difficulties, and the efficiency is not very high. For a memory leak problem, it may take multiple investigations and comparisons to find the cause of the problem. In order to find memory leaks quickly and easily, Square has open sourced LeakCanary based on MAT

In summary, LeakCanary is an effective, simple and easy-to-use tool for detecting memory leaks based on MAT.

insufficient

OOM problems caused by applying for large-capacity memory, Bitmap memory not being released, memory leaks in Service cannot be detected, etc. We need to use Mat.

Common memory leaks

I won’t open it for the time being, and then I will do this introduction. In fact, I summarized
memory leaks and solutions that are similar to minor exceptions . 1
Common memory leaks and solutions 2

Core principle

The SoftReference class, WeakReference class, and PhantomReference class represent soft references, weak references, and phantom references, respectively. The ReferenceQueue class represents a reference queue. It can be used in conjunction with these three reference classes. For example, if the object referenced by a weak reference is garbage collected, the Java virtual machine will put the weak reference in the ReferenceQueue associated with it, so If we check the queue when the object should be recycled, if it is found that there is this object in the queue, if the application indicates that the object has been recycled normally, if the detection is that there is no weak reference to the object, it does not mean that the object has not been recycled normally, it means a memory leak . But in actual operation, when we think that the object should be recycled, there may actually be a certain delay or GC does not occur, so we need to give him a certain collection time and retry time or manually trigger the GC (not necessarily executed).

use

After 2.0, you only need to add this in gradle. 2.4 is written in kotlin.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'

The work of registering install is put in AppWatcherInstaller.

The AppWatcherInstaller class inherits from ContentProvider, one of the four major components. AppWatcherInstaller$MainProcess has been registered
in leakcanary/leakcanary-object-watcher-android/src/main/AndroidManifest.xml in the leakcanary-object-watcher-android
submodule. MainProcess is a subclass of AppWatcherInstaller. AppWatcherInstaller is a sealed class.

Sealing characteristics:

  • Declare a sealed class to use the sealed modifier in the class name
  • All subclasses must be declared in the same file as the sealed class itself (extended classes of subclasses are not controlled by this)
  • A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members
  • Sealed classes are not allowed to have non-private constructors (its constructor defaults to private)
/**
 * Content providers are loaded before the application class is created. [AppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 */

internal sealed class AppWatcherInstaller : ContentProvider() {
	...省略代吗
  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()

...省略代吗
}
//注册
<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="${applicationId}.leakcanary-installer"     	
    android:enabled="@bool/leak_canary_watcher_auto_install"
    android:exported="false"/>

The feature of ContentProvider is that there is no need to display call initialization, it only needs to be registered in AndroidManifest.xml, and the onCreate() method of ContentProvider will be called before the onCreate of the application is executed. It is precisely to take advantage of this that LeakCanary writes the registration in it, and the system automatically calls it to complete it, completely unaware of the developer.
It can also be seen here that we understand the importance of the Android startup process of the four major component source codes. We can do many optimizations.

principle

When to detect leaks

First of all, we must first understand that LeakCanary-Android mainly detects memory leaks in Activity and Fragment. Leaks of other objects need to be done by ourselves.
Let’s take a look at his registered source code with questions.

Track the code registered in AppWatcherInstaller to 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)
  }
}

Continue to trace the code InternalAppWatcher.install(application) to 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 did ActivityDestroyWatcher.install
FragmentDestroyWatcher.install
We continue to trace the code details of FragmentDestroyWatcher and 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 is more complicated. In the install in FragmentDestroyWatcher, the FragmentManager is obtained through Activity in the onCreate listener initiated by Activity, and the listener is registered through FragmentManager.registerFragmentLifecycleCallbacks, mainly in the onFragmentViewDestroyed and onFragmentDestroyed methods. .

Among them, the Fragment in AndroidO and AndroidX and AndroidSupport libraries are adapted:
AndroidOFragmentDestroyWatcher
AndroidXFragmentDestroyWatcher
AndroidSupportFragmentDestroyWatcher The
specific code is not listed here. You can import the leakCanary yourself to see the code specifically.

How to detect leaks

Through the above analysis, we found that both Fragment and Activity monitor the destroy method and then execute the watch method of ObjectWatcher. It seems that the core detection code is inside. Then let's explore it now.

 objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )

Trace the objectWatcher.watch method.
During the tracking process, you may need to understand the four types of references, strong, weak, and weak,
which contains one of my questions: Why are Activity or Application wrapped by weak references and will not be recycled during GC?
It is more common in Android. We often encounter the need to use Context and its subclasses such as Application, Activity and other component classes. At this time, if strong references are used, memory leaks are prone to occur, and the use of soft references will cause problems. Necessary resources are wasted, so using weak references is the best solution at this time, because components with their own life cycles like Activity are all strongly referenced by lower-level objects, such as AM (ActivityManager), so When this type of object is still in its life cycle, it will not be easily recycled. Only after the life cycle is over will it be judged by the GC as having only weak references and eventually be recycled.

That is to say, when an object is referenced by strong and weak references at the same time, it will not be recycled during GC. Only after the end of its life cycle will it be determined by the GC as only weak references and eventually recycled.

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 and the detection ObjectWatch of Activity and Fragment
are defined in InternalAppWatcher. I mentioned this class before.

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

Then the last step of the code analysis (the reference object is not null after the five-second information of the handler marked with [Mark the last step] is cleared),
we directly trace the addOnObjectRetainedListener code in ObjectWatcher

@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
    onObjectRetainedListeners.add(listener)
  }

Place the mouse on this method and click on Command + touchpad, we will find that we are in the invoke method of InternalLeakCanary, but this place we have analyzed from the front and found no call there.

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

But don’t worry where it must be called. When we look at it, we pay attention to the main process. Ignore this. It’s okay. Let’s go back and see where it is used. After the front AppWatcherInstaller, AppWatcher, InternalAppWatcher,
finally we are in InternalAppWatcher. Found that he was called through reflection

internal object InternalAppWatcher {
 private val onAppWatcherInstalled: (Application) -> Unit
  ...省略代吗
  init {
  //我们创建InternalAppWatcher时init方法就会调用,onAppWatcherInstalled是kotlin中invoke约定的应用可以将invoke函数的lambda表达式赋值给一个变量,
Kotlin的约定有很多种,而比如使用便捷的get操作,以及重载运算符等等,invoke约定也仅仅是一种约定而已;
我们可以把lambda表达式或者函数直接保存在一个变量中,然后就像执行函数一样直接执行这个变量,
这样的变量通常声明的时候都被我们赋值了已经直接定义好的lambda,或者通过成员引用而获取到的函数;
但是别忘了,在面向对象编程中,一个对象在通常情况下都有自己对应的类,那我们能不能定义一个类,
然后通过构造方法来产生一个对象,然后直接执行它呢?这正是invoke约定发挥作用的地方。
我们只需要在一个类中使用operator来修饰invoke函数,这样的类的对象就可以直接像一个保存lambda表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。
我们还有另一种方式来实现可调用的对象,即让类继承自函数类型,然后重写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)
  }
...省略代吗
}

A detailed introduction to kotlinDSL and conventions is here

The init code block and construction method in Kotlin and the timing and execution sequence of the code in the companion object

Well, after the above has become divided, we already know where the InternalLeakCanary creates and registers the object not recovered listener (addOnObjectRetainedListener).
Next, we analyze what is called in the method after InternalLeakCanary implements the monitoring method.

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

  fun onDumpHeapReceived(forceDump: Boolean) {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onDumpHeapReceived(forceDump)
    }
  }

continue following

//调用
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)
  }

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

The summary is:
1) If the number of unrecovered objects is less than 5, wait for 5 seconds without any operation to check the unrecovered number again, and loop until it is greater than or equal to 5 or equal to 0, in order to prevent frequent heap recovery. Caton.
2) After more than 5, if it is in debug mode, it will wait another 20 seconds and execute the scheduleRetainedObjectCheck detection operation again. Prevent the debug mode from slowing down the recovery
3) Whether the last stack analysis is greater than or equal to 1 minute, if it is not more than one minute, it needs to be delayed again (1 minute-the current time since the last time) to perform the scheduleRetainedObjectCheck detection operation again

6. If the above conditions are met, you can start stack analysis
1), get the content file Debug.dumpHprofData (heapDumpFile.absolutePath)
2), objectWatcher.clearObjectsWatchedBefore (heapDumpUptimeMillis) This operation is to remove the previously analyzed The object, that is, remove the object that was not recycled before, and it is not within the scope of this analysis
3), HeapAnalyzerService opens the IntentService service for analysis
4), inserts the result into the database (the leak distinguishes the memory leak of the application itself and the class library Memory leak) and send notification

Well, here is the principle and source code analysis of LeakCanary

The old version of LeakCanary principle The analysis of the
old version of LeakCanary principle is super simple to understand!

Guess you like

Origin blog.csdn.net/u011148116/article/details/106762665