LeakCanary source code parsing

Memory leaks

Let’s take a look at the age-old problem of memory leaks and how LeakCanary can be detected.

Everyone is talking about memory leaks, so what is the root cause of memory leaks? The most fundamental reason is that the object is not recycled, resulting in a memory leak. To understand this, you need to understand Java’s garbage collection mechanism. What is garbage collection? The Java virtual machine (JVM) triggers garbage collection when it is running to reclaim objects that are not used and occupy memory. How does the Java virtual machine determine whether this object is useful? Is based on the GC ROOT reachability algorithm to determine. If an object is reachable through GC ROOT, then it is a useful object. Otherwise he’s useless.

reference

Either way, judging the survival of an object is all about references. The virtual machine now expects references to be stored in memory when there is enough space; If memory space is too tight after garbage collection, these objects can be discarded. So there are four kinds of references

1. Strong references: The garbage collector will not reclaim the referenced object as long as the strong reference is present.

2. Soft references: These objects are included in the collection scope for a second collection before the system is about to run out of memory.

3. Weak references: Objects referenced by weak references only survive until the next garbage collection occurs.

4. Virtual references: The existence of virtual references does not affect the lifetime of an object. There is no way to get an object instance through a virtual reference. The purpose of setting a virtual reference to an object is to receive a system notification when the object is reclaimed.

If an object is strongly referenced, then garbage collection does not collect that part of the object. At the same time, if the virtual machine determines that GC ROOT is unreachable and cannot reclaim the object, it can be said that the object is causing a memory leak. Because there is no place to use this object, and the object is still occupying memory, it causes memory waste, which is called memory leak.

Weak references and reference queues

fun main(a) {
    val referenceQueue = ReferenceQueue<Pair<String, Int>? > ()var pair: Pair<String, Int>? = Pair("Yellow".24)
    val weakReference = WeakReference(pair, referenceQueue)

    println(referenceQueue.poll()) //null

    pair = null

    System.gc()
    // Hibernate for a period of time after GC, waiting for the pair to be reclaimed
    Thread.sleep(4000)

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

It can be seen that after GC, the return value of Referencequeue.poll () becomes non-null, which is caused by a combination of WeakReference and referenceQueue: When a WeakReference object is declared, if ReferenceQueue is also passed as a construction parameter, then when the object held by WeakReference is recovered by GC, the JVM will store the WeakReference into the ReferenceQueue associated with it. With this feature, we can implement memory leak detection

For example, when the user presses the back button to exit the Activity, the Activity object should normally be reclaimed by the system shortly after. We can listen for the Activity’s onDestroy callback, When calling back, the Activity object is saved in the WeakReference associated with ReferenceQueue. After a period of time (it can actively trigger several GC), whether there is a value in the ReferenceQueue is detected. If it is always null, a memory leak has occurred. LeakCanary is done this way.

LeakCanary source analysis

We’ve explained some of the prerequisites to help you better understand how LeakCanary works. Here is the workflow and details of LeakCanary. This blog post is based on version 2.8.1.

LeakCanary 2.8.1 version, LeakCanary the initial process to the ContentProvider MainProcessAppWatcherInstaller to completion

MainProcessAppWatcherInstaller#onCreate
// This is a ContentProvider
internal class MainProcessAppWatcherInstaller : ContentProvider() {
    
  override fun onCreate(a): Boolean {
    valapplication = context!! .applicationContextas Application
    AppWatcher.manualInstall(application)
    return true}}Copy the code

Since the ContentProvider is initialized by the system calling its onCreate() method before the Application is created, So LeakCanary uses the AppWatcherInstaller to initialize the Context and launch with the application, simplifying the import cost for the user.

MainProcessAppWatcherInstaller will eventually Application objects to AppWatcher manualInstall (Application) method

AppWatcher#manualInstall
@JvmOverloads
fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5).// This is also a default parameter, 5 seconds
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)// There are default arguments
) {
    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()
    }
   // Initialize some configuration, InternalLeakCanary class invoke method, configure leak Listener, GC trigger, Dump class.
    LeakCanaryDelegate.loadLeakCanary(application)

    // To call the various installments, we register some listening events for the various InstallableWatcher. Here are four InstallableWatcher
    watchersToInstall.forEach {
        it.install()
    }
}
Copy the code
AppWatcher#appDefaultWatchers
fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}
Copy the code

You can see that there are four types of InstallableWatcher. The implementation is pretty much the same

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(a) {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall(a) {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}
Copy the code

ActivityWatcher calls the install method to register for a listener. LifecycleCallbacks when acrivityWatcher onDestory occurs, It calls back to onActivityDestroyed. And it is called reachabilityWatcher expectWeaklyReachable. So take a look at this function to see how LeakCanary actually works. Before we do that, let’s look at what reachabilityWatcher is, and let’s look at appDefaultWatchers, which is just objectWatcher.

Initialization of objectWatcher
val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
        check(isInstalled) {
            "AppWatcher not installed"
        }
        mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true})Copy the code

So expectWeaklyReachable is actually the expectlyreachable function of ObjectWatcher

ObjectWatcher#expectWeaklyReachable

This is a function that we need to look at step by step

  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if(! isEnabled()) {return
    }
    // Remove objects that did not leak first, ignoring them the first time
    removeWeaklyReachableObjects()
    // Generate a key
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
   // With this key, a weak reference object is constructed and a reference queue is passed in
    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"
    }
	// Then store the weak reference object into watchedObjects
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)// This method will delay execution by 5 seconds}}Copy the code
ObjectWatcher#moveToRetained
@Synchronized private fun moveToRetained(key: String) {
    // Remove objects that do not leak
    removeWeaklyReachableObjects()
    // If weak references to drinking can still be found in watchedObjects after removal, the object has leaked
    val retainedRef = watchedObjects[key]
    if(retainedRef ! =null) {
        retainedRef.retainedUptimeMillis = clock.uptimeMillis()// Record the current time
        onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}
Copy the code
ObjectWatcher#removeWeaklyReachableObjects
private fun removeWeaklyReachableObjects(a) {
   // From the weak references and reference queues section, we can know that if the objects in the queue are not leaked objects. The operation here is to traverse the object in the queue, and then remove the object in the watchedObjects corresponding to the key based on its key. If the corresponding key exists in watchedObjects after this operation, then the object was not placed in the reference queue at the time of GC, that is, the object was not reclaimed, so you can be sure that the object caused a memory leak
    var ref: KeyedWeakReference?
    do {
        ref = queue.poll() as KeyedWeakReference?
        if(ref ! =null) {
            watchedObjects.remove(ref.key)
        }
    } while(ref ! =null)}Copy the code

MoveToRetained method is used to determine whether the object associated with the key has been leaked. If not, remove the weak reference to the object, and update its retainedUptimeMillis value if it has been leaked. At the same time analyze the memory leak chain by calling onObjecTre谴 责.

That’s our source code analysis for LeakCanary on how to detect memory leaks. In fact, the detection of memory leaks in the part of the code is relatively simple, the difficulty is to obtain the memory leak chain and the Dump part. However, these two parts are not in the scope of today’s discussion. If there is an opportunity later, we can take them out separately. Well today’s blog so much, like a small partner point attention and praise it.