leakcanary icon indicating copy to clipboard operation
leakcanary copied to clipboard

Implement BLeak for Android

Open pyricau opened this issue 5 years ago • 1 comments

This is inspired by the BLeak paper from @jvilk

High level, this requires looping on a scenario, and comparing the state of the heap between each run, looking for paths with a growing count of outer refs. There's also second step / extra run to capture stacktraces of the growing of outer refs.

This would likely be a testing or benchmarking utility, where you have to first identify a potentially problematic scenario then run this to detect the cause. However we could also imagine a production variant where we take heap dumps when the app enters background / screen off, IF the app is in a somewhat identical state to what it was at the last background.

Now here's the good news: all of this was already implemented for Android Studio: https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:bleak/src/com/android/tools/idea/bleak/Bleak.kt;l=30-44;drc=49da46f9ed909e9073f98541832a0ffcc5d62f4a

This doesn't actually rely on heap dumps, and instead uses JVM TI native code to pause all threads and get GC roots, and then reflection to navigate the roots. Since Android 8 we have ART TI that provides the same capabilities. Reflection could be an issue due to blacklist APIs, but that can be worked around:

  private fun disableApiBlacklist() {
    if (Build.VERSION.SDK_INT < 28) {
      return
    }
    val forName = Class::class.java.getDeclaredMethod("forName", String::class.java)
    val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java)
    val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*>
    val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
    val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) as Method
    val vmRuntime = getRuntime.invoke(null)
    setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
  }

There's also the concept of Expander which wasn't mentioned in the BLeak paper. My understanding is that these swap out parts of the raw object graph with higher level constructs for known types (e.g. collections, etc). It's called expander because they've turned it around, instead of swapping out it's the thing that provides outer refs (ie expands).

It looks like they haven't implemented the stacktrace capturing piece of BLeak. I'm not sure if it's necessary in a Java context, i.e. knowing where a leak is might be enough to figure out what the problem is. However we could maybe implement it with the ART TI API to replace classes (not sure).

pyricau avatar Dec 29 '20 09:12 pyricau

PR : https://github.com/square/leakcanary/pull/2585

pyricau avatar Nov 14 '23 22:11 pyricau