I recently came across this comment in a post:
The thing really annoying about LeakCanary or Android Studio, most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks, I wonder if LeakCanary is showing false positives or Android Studio is missing positives.
That's a good question, let's dig into code and figure this out!
False positive leaks in Android Studio
Before answering the question, we need to talk about where the idea of false positive leaks comes from: Android Studio.
That warning was originally a longer description:
Activity and Fragment instances that might be causing memory leaks. For Activities, these are instances that have been destroyed but are still being referenced. For Fragments, these are instances that do not have a valid FragmentManager but are still being referenced. Note, these instance might include Fragments that were created but are not yet being utilized.
The documentation provides more insights on false positive leaks:
In certain situations, such as the following, the filter might yield false positives:
- A
Fragment
is created but has not yet been used.- A
Fragment
is being cached but not as part of a FragmentTransaction.
The phrasing is vague but it looks like false positive leaks only applies to Fragments.
Android Studio leak filtering
Android Studio dumps and analyzes the heap when you press the Dump Heap icon.
Leaking instances are displayed by enabling the "Activity/Fragment Leaks" filter, which updates the bottom panel to only show leaking instances. The filtering is performed by ActivityFragmentLeakInstanceFilter:
const val FRAGFMENT_MANAGER_FIELD_NAME = "mFragmentManager"
/**
* A Fragment instance is determined to be potentially leaked if
* its mFragmentManager field is null. This indicates that the
* instance is in its initial state. Note that this can mean that
* the instance has been destroyed, or just starting to be
* initialized but before being attached to an activity. The
* latter gives us false positives, but it should not uncommon
* as long as users don't create fragments way ahead of the time
* of adding them to a FragmentManager.
*/
private fun isPotentialFragmentLeak(
instance: InstanceObject
): Boolean {
return isValidDepthWithAnyField(
instance,
{ FRAGFMENT_MANAGER_FIELD_NAME == it },
{ it == null }
)
}
/**
* Check if the instance has a valid depth and any field
* satisfying predicates on its name and value
*/
private fun isValidDepthWithAnyField(
inst: InstanceObject,
onName: (String) -> Boolean,
onVal: (Any?) -> Boolean
): Boolean {
val depth = inst.depth
return depth != 0 && depth != Int.MAX_VALUE &&
inst.fields.any {
onName(it.fieldName) && onVal(it.value)
}
}
(yes, Fragfment manager)
So, a fragment is considered leaking if its mFragmentManager
field is null and the fragment is reachable via strong references (that's what valid depth means in the above code). If you create a fragment instance but don't add it, it would be reported as a leak, hence the warning about false positive leaks.
How LeakCanary finds fragment leaks
Unlike Android Studio, LeakCanary does not look at the mFragmentManager
field for all fragment instances in memory. LeakCanary hooks into the Android lifecycle to automatically detect when fragments are destroyed and should be garbage collected. These destroyed objects are passed to an ObjectWatcher
, which holds weak references to them.
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
objectWatcher.watch(
fragment,
"Received Fragment#onDestroy() callback"
)
}
},
true
)
}
If the weak reference held by ObjectWatcher
isn't cleared after waiting 5 seconds and running garbage collection, the watched object is considered retained, and potentially leaking. LeakCanary dumps the Java heap into a .hprof
file (a heap dump)
Then LeakCanary parses the .hprof
file using Shark and locates the retained objects in that heap dump.
And this is where LeakCanary differs from Android Studio: it looks for the custom weak references that it created (KeyedWeakReference) and checks their referent
field in KeyedWeakReferenceFinder:
/**
* Finds all objects tracked by a KeyedWeakReference, i.e. al
* objects that were passed to ObjectWatcher.watch().
*/
object KeyedWeakReferenceFinder : LeakingObjectFinder {
override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
return graph.findClassByName("leakcanary.KeyedWeakReference")
.instances
.map { weakRef ->
weakRef["java.lang.ref.Reference", "referent"]!!
.value
.asObjectId
}
.filter { objectId ->
objectId != ValueHolder.NULL_REFERENCE
}
.toSet()
}
}
That means LeakCanary only surfaces fragments that are actually leaking.
Why LeakCanary reports more leaks
Now that we've established that LeakCanary does not have the same false positive leaks as Android Studio, let's go back to the main observation:
most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks
The explanation is in the LeakCanary documentation:
LeakCanary automatically detects leaks for the following objects:
- destroyed
Activity
instances- destroyed
Fragment
instances- destroyed fragment
View
instances- cleared
ViewModel
instances
Android Studio does not detect leaks for destroyed fragment View
instances or cleared ViewModel
instances. The former is actually a common cause of leak:
Adding a
Fragment
instance to the backstack without clearing that Fragment’s view fields inFragment.onDestroyView()
(more details in this StackOverflow answer).
Conclusion
If LeakCanary reports a leak trace, then there is definitely a leak. LeakCanary is always right! Unfortunately, we can't blame false positive leaks on the little yellow bird as an excuse for not fixing leaks.
Top comments (0)