preface
LeakCanary is a simple and convenient memory leak detection framework that many students have already used. It is very convenient to use and has the following features: 1. No manual initialization is required. 2. Memory leaks can be detected automatically and alarms can be sent through notification 3. Cannot be used online
LeakCanary how does LeakCanary detect memory leaks and how does LeakCanary initialize? 3. How does LeakCanary find a memory leak? 4. Why does LeakCanary not work online?
This article reviews the main process for LeakCanary memory leak detection and answers the above questions
1. LeakCanary
Principles and basic flow of memory leak detection
1.1 Principles of Memory Leakage
Causes of memory leaks: Objects that are no longer needed are still referenced, so that the memory allocated for the object cannot be reclaimed.
For example: aActivity
The instance object is being calledonDestory
Method is no longer needed if a reference is storedActivity
The static field of the object will result inActivity
Cannot be collected by garbage collector.
The reference chain comes from the garbage collector’s reachability analysis algorithm: when an object arrivesGC Roots
When no reference chain is attached, the object is proved to be unavailable. As shown in figure:
objectobject5
,object6
,object7
They’re related to each other, but they go toGC Roots
Are unreachable, so they will be judged to be recyclable objects.
在Java
Language, can be asGC Roots
Objects include the following:
- The object referenced in the virtual machine stack (the local variable table in the stack frame).
- Objects referenced by static properties in the method area.
- The object referenced by the constant in the method area.
- Objects referenced by JNI (commonly referred to as Native methods) in the Native method stack.
1.2 LeakCanary
Basic process for detecting memory leaks
Knowing how memory leaks work, we can guess what the basic flow of LeakCanary looks like. Trigger detection (objects that are no longer needed) after the page is closed. 2. Trigger GC and get objects that still exist, which are likely to leak. 3. Store the results and use notifications to alert users to leaks
The overall flow chart is as follows:
- 1.
ObjectWatcher
Created aKeyedWeakReference
To monitor objects. - 2. Later, in the background thread, the delay checks to see if the reference has been cleared and fires if it has not
GC
- 3. If the reference has not been cleared, it will
dumps the heap
To a.hprof
File, and then will.hprof
The file is stored to the file system. - 4. The analysis process is mainly in
HeapAnalyzerService
In,Leakcanary2.0
The use ofShark
To resolvehprof
File. - 5.
HeapAnalyzer
To obtainhprof
All of theKeyedWeakReference
And getobjectId
- 6.
HeapAnalyzer
To calculateobjectId
toGC Root
Is the shortest strong reference link path to determine whether there is leakage, and then build the reference chain causing leakage. - 7. Store the analysis results in the database and display the leak notification.
Here is just a general introduction, the detailed process can be read below
2. LeakCanary
How is it installed automatically?
LeakCanary is easy to use and can be automatically initialized just by adding dependencies. How does this work? Let’s take a look at the source code, in fact, mainly through ContentProvider implementation
internal sealed class AppWatcherInstaller : ContentProvider() {
/** * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
internal class MainProcess : AppWatcherInstaller(a)/** * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`, * [LeakCanaryProcess] automatically sets up the LeakCanary code */
internal class LeakCanaryProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
valapplication = context!! .applicationContextas Application
AppWatcher.manualInstall(application)
return true}}Copy the code
When we start the App, the general startup sequence is: Application->attachBaseContext =====>ContentProvider->onCreate =====>Application->onCreate The ContentProvider is initialized before application.oncreate, which calls the initialization method of LeakCanary for manual initialization
2.1 Cross-process Initialization
Note that The AppWatcherInstaller has two subclasses,MainProcess and LeakCanaryProcess. MainProcess is used by default and will be used when the App process is initialized. When the leakcanary- Android-Process module needs to be initialized by a separate process, it will be deactivated in a new process
2.2 LeakCanary2.0
Manual initialization method
LeakCanary takes time to detect a memory leak and interrupts the App operation, which is not a great experience when detection is not required, so although LeakCanary can be automatically initialized, we actually need to manually initialize it sometimes
LeakCanary automatic initialization can be turned off manually
<resources>
<bool name="leak_canary_watcher_auto_install">false</bool>
</resources>
Copy the code
1. Then call AppWatcher. ManualInstall when you need to initialize 2. Config = LeakCanary. Config. copy(dumpHeap = false) 3. Begin the desktop icon: rewrite r. ool. Leak_canary_add_launcher_icon LeakCanary. Or call showLeakDisplayActivityLauncherIcon (false)
2.3 summary
LeakCanary is initialized with ContentProvier. ContentProvier is normally loaded before application.oncreate, and LeakCanary has called AppWatcher. ManualInstall in its onCreate() method to initialize it. This eliminates the initialization step, but can cause problems with StartUp time. Users have no control over the timing of initialization, which is why Google has launched StartUp. However, for LeakCanary this problem is not serious, as it is only relied on during the Debug phase
3.LeakCanary
How do I detect memory leaks?
3.1 First let’s look at what is done during initialization.
When we initialize, we call AppWatcher. ManualInstall. Let’s look at this method
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
....
watchersToInstall.forEach {
it.install()
}
}
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
Copy the code
As you can see, the initialization is installed some Watcher, namely, by default, we can only observe the Activity, fragments, RootView, Service whether these objects are leaking If need to see other objects, the need to manually add and processing
3.2 LeakCanary
How do I trigger detection?
As mentioned above, some Watcher is installed during initialization, using ActivityWatcher as an example
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
As you can see, detection for memory leaks is triggered when Activity. OnDestory is triggered
3.3 LeakCanary
How do I detect objects that might leak?
Can be seen from the above, the Activity will call after being closed to ObjectWatcher. ExpectWeaklyReachable
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if(! isEnabled()) {return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
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"
}
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
private fun removeWeaklyReachableObjects(a) {
// 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)}Copy the code
1. All incoming observations are stored in watchedObjects 2. A KeyedWeakReference weakreference is generated for each watchedObject and associated with a queue. When the object is reclaimed, the weakReference goes to queue 3. In the process of testing, we will call removeWeaklyReachableObjects for many times, has recycled object removed from the watchedObjects 4. If watchedObjects does not remove the object, proving that it was not reclaimed, moveToRetained is called
3.4 LeakCanary
A heap snapshot is generatedhprof
file
MoveToRetained later, you can call to HeapDumpTrigger. CheckRetainedInstances checkRetainedInstances method () method is to determine the leakage of the last method. This verifies that the reference is really leaked, and if so, heap dump is initiated, the dump file is analyzed, and the chain of references is found
private fun checkRetainedObjects(a) {
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if(elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { onRetainInstanceListener.onEvent(DumpHappenedRecently) .return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility")}private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
){... heapDumper.dumpHeap() .... lastDisplayedRetainedObjectCount =0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
Copy the code
1. If the retainedObjectCount is greater than 0, do a GC to avoid additional Dump 2. By default, if retainedReferenceCount<5, no Dump is performed, saving resource 3. If the time between two dumps is less than 60 seconds, the system returns directly to avoid frequent Dump 4. Call heapDumper.dumpheap () for the actual Dump operation 5. after Dump, delete the already processed reference 6. Call HeapAnalyzerService. RunAnalysis analyze the results
3.5 LeakCanary
How to analyzehprof
file
Analysis of thehprof
Documentation works mainly inHeapAnalyzerService
Class
aboutHprof
The parsing details of the file need to be involvedHprof
Binary protocol, by reading the protocol documentation,hprof
The binary file structure is as follows:
The analysis process is as follows:
A brief description of the process:
1. Parse the file header to obtain the start position for parsing
2. Create the vm based on the header informationHprof
The file object
3. Create a memory index
Use 4.hprof
Object and index buildingGraph
object
5. Find the objects that may be leaked andGCRoot
To determine whether there is leakage (using the breadth-first algorithm inGraph
Find the)
Leakcanary2.0 is one of the biggest changes to Leakcanary2.0 from the previous version. The main idea behind Leakcanary2.0 is to parse the contents of the hprof file into a graph data structure according to the binary protocol of the hprof file, and then breadth through the graph to find the shortest path. The path starts with the GCRoot object and ends with the leaked object
The implementation principle of Android Memory Leak Detection LeakCanary2.0 (Kotlin version) is described
3.6 Storage and notification of leakage results
The results of the storage and mainly done in DefaultOnHeapAnalyzedListener notice
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
val db = LeaksDbHelper(application).writableDatabase
val id = HeapAnalysisTable.insert(db, heapAnalysis)
db.releaseReference()
...
if (InternalLeakCanary.formFactor == TV) {
showToast(heapAnalysis)
printIntentInfo()
} else {
showNotification(screenToShow, contentTitle)
}
}
Copy the code
Two main things are done: 1. Store leak analysis results in the database; 2. Display notifications to remind users to look for memory leaks
4. WhyLeakCanary
Cannot be used online?
After understanding the work that has been done to LeakCanary determination objects, it is not difficult to see that applying LeakCanary directly online has some of the following problems: 1. After each memory leak, a.hprof file is generated, parsed, and the result is written to.hprof.result. Increase the burden of mobile phones, causing problems such as mobile phone congestion. 2. Multiple calls to GC may affect online performance. 3. The same leakage problem will cause repeated generation of.hprof files, repeated analysis and writing to disk. 4.. Hprof files are large and information retrieval is a problem.
Knowing these problems, we can try to put forward some solutions: 1. We can set a memory threshold M according to the mobile phone information. When the used memory is less than M, if there is a memory leak at this time, only the information of the leaked object is stored in the memory, and the.hprof file is not generated. If the number of used links is greater than M, a. Hprof file is generated. 2. 3. Instead of retrieving the. Hprof file directly, you can retrieve the analysis result. You can try to store leaked objects in the database and detect the same leak only once for each user to reduce the impact on users
The above ideas have not been tested, just for readers’ reference
conclusion
When we introduce LeakCanary, it will automatically install and start analyzing memory leaks and alarm by following steps 1. Automatic installation 2. detection of possible leaking objects 3. heap snapshot, generate hprof file 4. Analyze the Hprof file. 5. Classify and notify leaks
This article looks at the main process of LeakCanary and some of the questions raised at the beginning of this article
The resources
LeakCanary2.0 (Kotlin version) is the latest LeakCanary detection solution for Android. Completely based on Kotlin refactoring and upgrading! Why use LeakCanary to detect memory leaks?