LeakCanary source code parsing
preface
As for memory leak detection, MAT has a high starting point, so we generally use LeakCanary as our memory leak detection tool.
Basic knowledge of
Four types of references
LeakCanary is implemented primarily by monitoring the recovery of activities and fragments that have been destroyed based on weak references.
-
Strong references: Will not be recycled under any circumstances.
-
Soft reference: Enough memory not to reclaim. When there is not enough memory, it is reclaimed.
-
Weak references: When garbage is collected, it is directly collected.
-
Virtual references: Garbage collection is directly collected.
ReferenceQueue (ReferenceQueue)
Both soft and weak references can be associated with a reference queue. When the referenced object is reclaimed, the soft reference is added to the reference queue associated with it. The basic implementation of LeakCanary is to place the corresponding instances of destroyed activities and fragments into weak references and associate a reference queue. If the instance is reclaimed, the weak reference is placed in the ReferenceQueue, and if the monitored instance does not appear in the ReferenceQueue after a certain period of time, then a memory leak has occurred that has prevented the instance from being reclaimed.
Method of use
Configuration:
dependencies {
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 1.6.3'
releaseImplementation 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com. Squareup. Leakcanary: leakcanary - support - fragments: 1.6.3'
}
Copy the code
Use:
public class ExampleApplication extends Application {
@Override public void onCreate(a) {
super.onCreate();
LeakCanary.install(this);
}
}
Copy the code
Leakcanary Works correctly
Analyze from a unique entry point in the program. This article is based on the 1.6.3 version of the source code analysis. The corresponding source code address is leakcanary-source.
Register instance monitoring
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application)// Create a constructor for reference monitoring to be used on the Android side
.listenerServiceClass(DisplayLeakService.class)
// Sets class reference objects that are not monitored
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
// Create a monitor for references
.buildAndInstall();
}
Copy the code
This method is short, so let’s break it down one by one.
Construct an AndroidRefWatcherBuilder object
Create an AndroidRefWatcherBuilder object
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
Copy the code
The AndroidRefWatcherBuilder object created here is a constructor for reference monitoring on the Android side.
Set the listener class in the background
//AndroidRefWatcherBuilder.java
// Set up a class to listen for the results of the analysis.
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
// Set a listener
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
//RefWatcherBuilder.java
//HeapDump listener
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
Copy the code
Here, the DisplayLeakService class serves as our final memory leak analyst and is capable of notifying memory leak messages (typically Notification).
References not included in monitoring
The excludedRefs method removes references that we don’t care about from our monitoring scope. This is done primarily because of some system-level referencing issues. We can take a look at what’s in there that we don’t need to pay attention to.
// Since the Android AOSP itself may have memory leaks, these things are not alerted by default.
public static @NonNull ExcludedRefs.Builder createAppDefaults(a) {
// All enumerated types of AndroidExcludedRefs are taken into account.
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
// Iterate over all enumerated types
for (AndroidExcludedRefs ref : refs) {
// If the enumeration type performs the exclusion of references
if (ref.applies) {
// Call the enumeration's add method, which places all reference classes to be excluded in and out of the excluede
ref.add(excluded);
((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
}
}
return excluded;
}
Copy the code
This might be a little confusing, but let’s take a quick look at the AndroidExcludedRefs class.
public enum AndroidExcludedRefs {
// parameter that identifies whether the add method needs to be executed
final boolean applies;
AndroidExcludedRefs() {
this(true);
}
AndroidExcludedRefs(boolean applies) {
this.applies = applies;
}
// Enumerate the methods the class needs to implement
abstract void add(ExcludedRefs.Builder excluded);
}
Copy the code
AndroidExcludedRefs is an enumerated type. Contains member variables labelled as well as the add() method.
Let’s examine one more concrete enumeration type.
//AndroidExcludedRefs.java
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override
void add(ExcludedRefs.Builder excluded) {
// Sets an attribute in the excluded class
excluded.instanceField("android.app.ActivityThread$ActivityClientRecord"."nextIdle")
// Set the reason for exclusion
.reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"
+ " nextIdle client record in the android.app.ActivityThread.mActivities map."
+ " Not sure what's going on there, input welcome.");
}
},
Copy the code
ACTIVITY_CLIENT_RECORD__NEXT_IDLE is a concrete enumeration type. Applies to SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP. There is also a concrete implementation of the Add method. The implementation adds the reference types that need to be excluded to Excluded.
So when SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP, we execute add.
AndroidExcludedRefs has different enumeration instances that are processed differently depending on the system version. The main point here is to ensure that some system-level memory leaks are no longer flagged.
Create a monitor for references
Let’s look directly at how an Activity that has performed onDestroy is monitored in buildAndInstall.
// Return a RefWatcher object based on the Settings
public @NonNull RefWatcher buildAndInstall(a) {
if(LeakCanaryInternals.installedRefWatcher ! =null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// Create a RefWatcher object using the build() method in constructor mode, which has many default Settings
RefWatcher refWatcher = build();
if(refWatcher ! = DISABLED) {
// If the memory leak Activity is allowed to be displayed, it is processed
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
// If the listener is set up, register a lifecycle listener for the Activity
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// If a listening Fragment is set, register life cycle listening for the Fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
Copy the code
Let’s look at how to monitor activities and fragments.
- Handling of activities
//ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
// Create a weak reference listener class for the Activity
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// Register an Activity lifecycle listener for the incoming Application
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
Copy the code
Here to create a ActivityRefWatcher object, and then apply for, through registerActivityLifecycleCallbacks register a listener callback.
//ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
// Only listen on the deStory method. Add the activity that calls destory to the listener watcher
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
Copy the code
In this listener method, only the Activity’s onDestroy method is listened for. Use refWatcher to monitor instances when an Activity is destroyed.
- Handling of fragments
//FragmentRefWatcher.java
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
// Add the two implementation classes that implement the FragmentRefWatcher interface to FragmentRefWatcher
// Two implementation classes, one is for listening on the Fragment under V4 package, the other is for listening on the Fragment under V4 package
if (SDK_INT >= O) {
/ / implementation class AndroidOFragmentRefWatcher
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
/ / implementation class SupportFragmentRefWatcher listens for V4 fragments under the package
/ / here using reflection, because SupportFragmentRefWatcher this class in support - fragments of this module.
// So, if we hadn't introduced V4, we wouldn't have introduced this class.
Class<? > fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<? > constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher = (FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
// If there is no Fragment monitor, return directly
if (fragmentRefWatchers.size() == 0) {
return;
}
// Create a Helper instance
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
// Register the Activity's lifecycle callback
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
Copy the code
Since we often use two types of fragments, one is the Fragment in the Support package and the other is the Fragment in the standard App package. So we’re dealing with both of these.
Let’s look at the lifecycle functions for registration
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
// The watchFragments method of the concrete implementation class is called here. The concern here is the onCreate method of the bound Activity. At this point, the corresponding FragmentManager object has been created
/ / and registerFragmentLifecycleCallbacks by FragmentManager object can be to create the Fragment lifecycle to monitor for its management
watcher.watchFragments(activity);
}
}
};
Copy the code
Here we also register the Activity’s lifecycle callback. But what you’re monitoring here is the onActivityCreated method. Let’s look at the implementation of the watchFragments here.
Specific implementation, there are two classes, one is SupportFragmentRefWatcher, one is AndroidOFragmentRefWatcher. We’ll just analyze the first one here. The remaining Fragment is similar, but varies depending on the Fragment used.
public void watchFragments(Activity activity) {
// Fragments in V4 packages must be handled using FragmentActivity
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if(view ! =null) {
// Start monitoring when the fragment view is destroyed
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
// Start monitoring when the fragment is destroyed
refWatcher.watch(fragment);
}
};
Copy the code
So here by getting the Activity of FragmentManager, for its management by registerFragmentLifecycleCallbacks fragments of life cycle for listening. When the Fragment performs destruction, its reference is added to the monitor queue.
So far, our activities and fragments have been monitored through refWatcher’s Watch.
Then, we next analyze how the watch method monitors instances and determines the existence of memory leaks.
monitoring
We will monitor the destroyed interface through the Watch method of refWatcher.
//RefWatcher.java
public void watch(Object watchedReference) {
// Override the method
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
// Make sure the watch object is not empty
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// Create a UUID
String key = UUID.randomUUID().toString();
// Save the UUID to set
retainedKeys.add(key);
// Create a weak reference to the object to be detected.
// If the weak reference is reclaimed, the reference will be added to the queue
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
// Determine whether reference is reclaimed
ensureGoneAsync(watchStartNanoTime, reference);
}
Copy the code
There are three main operations performed in this
- Create the UUID
- Save the generated UUID to the retainedKeys queue.
- Creates a weak reference, specifying the corresponding reference queue.
The retainedKeys queue here records the reference objects that we monitored. The queue holds the reclaimed references. So by comparing the two, we can find references to memory leaks.
Let’s take a look at how this process is performed in ensureGoneAsync.
//RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run(a) {
return ensureGone(reference, watchStartNanoTime);
}
});
}
Copy the code
The watcheExecute here uses AndroidWatchExecutor
//AndroidRefWatcherBuilder.java
@Override protected @NonNull WatchExecutor defaultWatchExecutor(a) {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
Copy the code
Let’s trace the execute method.
//AndroidWatchExecutor.java
@Override public void execute(@NonNull Retryable retryable) {
// If the current thread is the main thread, execute waitForIdl directly
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
// If waitForIdle is not in the main thread, it is put into the main thread by the Handler mechanism
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
// Use the Handler mechanism to send waitForIdle to the main thread for execution
mainHandler.post(new Runnable() {
@Override public void run(a) {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// Add a handler when messagequeue is idle. This approach is primarily for performance and does not affect our normal application fluency
/ / this method can be carried in the main thread, so postToBackgroundWithDelay are the main thread of execution
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle(a) {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
Copy the code
So it can eventually performs postToBackgroundWithDelay method in the main thread.
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
// Calculate the compensation factor. If a retry is returned, the failedAttempts will increase, increasing the execution delay of the method.
For example, if the first execution is RETRY in 5 seconds, the next execution will be delayed by 10 seconds
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
// Calculate the delay time
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
//backgroundHandler will execute the code in the run method in a new thread.
backgroundHandler.postDelayed(new Runnable() {
@Override public void run(a) {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
Copy the code
The execution of this method delays the execution of the corresponding run method based on the number of times it executes.
Let’s look at the execution of the retryable. Run () method. Which brings us back to the ensureGoneAsync method in our RefWatcher.
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run(a) {
return ensureGone(reference, watchStartNanoTime);
}
});
}
Copy the code
The ensureGone method here is really the core of our code.
// Determine whether reference is reclaimed
@SuppressWarnings("ReferenceEquality")
// Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// Remove reclaimed monitored objects
removeWeaklyReachableReferences();
// If the current state is debug, retry is returned
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// The monitoring object has been reclaimed
if (gone(reference)) {
return DONE;
}
// Perform a garbage collection
gcTrigger.runGc();
// Remove the reclaimed monitored object
removeWeaklyReachableReferences();
if(! gone(reference)) {
// If there is still no collection, a memory leak has occurred
long startDumpHeap = System.nanoTime();
// The duration of gc execution
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump hprof file
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
// If the snapshot file cannot be generated, try again
return RETRY;
}
// The time spent generating the hprof file
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// heapdumpListener is ServiceHeapDumpListener by default
heapdumpListener.analyze(heapDump);
}
return DONE;
}
Copy the code
This code performs several procedures
- Example Remove reclaimed monitored objects
- If the currently monitored object has been reclaimed, return DONE.
- If there is no collection, a GC operation is forced.
- Remove reclaimed monitored objects again.
- If the current monitored object is still not reclaimed, the hprof file is dumped and the memory leak is analyzed based on the snapshot file.
Here we analyze each method one by one
Removes a reclaimed weak reference object
private void removeWeaklyReachableReferences(a) {
KeyedWeakReference ref;
/ / circular queue
while((ref = (KeyedWeakReference) queue.poll()) ! =null) {
// The ref in the queue has been reclaimed, so remove the corresponding key from the retainedKeys.
retainedKeys.remove(ref.key);
}
}
Copy the code
The queue here is the reference queue we mentioned, and the retainedKeys hold the objects we want to monitor. When objects are reclaimed, weak references are stored in the queue, so we remove weak references from the queue from the retainedKeys. All that’s left is objects that we’re listening for or that have memory leaks.
Check whether the monitored object is reclaimed
// Check whether the monitored object is reclaimed true: The monitored object is reclaimed
private boolean gone(KeyedWeakReference reference) {
return! retainedKeys.contains(reference.key);
}
Copy the code
In the previous step, we removed the reclaimed reference information from the retainedKeys, so we can simply determine if the class we are monitoring is present in the set.
Export the. Hprof file
public File dumpHeap(a) {
// Create an.hrof file
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
// Failed to create, try again later
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
// Toasts are displayed on the main thread through the Handler mechanism, using CountDownLatch. [Fixed] Toasts are displayed with a value of 0,
showToast(waitingForToast);
// Wait for the main thread to display a Toast (CountDownLatch becomes 0). Then you can continue
if(! waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
// Create a Notification Notification
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
// Create a snapshot of the heap to see which parts of the program are using most of the memory
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
// Turn off Toask and Notification notifications
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
Copy the code
A.hprof file will be created, and a Toast and Notification Notification will be displayed, and the snapshot information of the heap during the memory leak will be saved in the.hprof file, and the Toast and Notification notifications will be closed. So after doing this, the.hprof file we generated contains information about the heap at the time of the memory leak.
Snapshot File Analysis
After a snapshot file is generated, heapdumpListener is used to analyze the generated snapshot file. The default listener is the ServiceHeapDumpListener class
//AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener(a) {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
Copy the code
Let’s look at its analyze method
//ServiceHeapDumpListener.java
public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
//HeapAnalyzerService.java
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
Intent intent = new Intent(context, HeapAnalyzerService.class);
// listenerServiceClass is DisplayLeakService
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
// Start a foreground service. When started, the onHandleIntent method is called, which is implemented in the parent class. Implementation will call onHandleIntentInForeground () method
ContextCompat.startForegroundService(context, intent);
}
Copy the code
A service is started to analyze files. The onHandleIntent method is called when the service is started. The onHandleIntent of HeapAnalyzerService is implemented in its parent class.
//ForegroundService.java
@Override protected void onHandleIntent(@Nullable Intent intent) {
onHandleIntentInForeground(intent);
}
Copy the code
So will call onHandleIntentInForeground this method.
protected void onHandleIntentInForeground(@Nullable Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// Create a heap parser
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//** focus analysis method *** analysis of memory leak results
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
// Call the interface and call the result back to the corresponding class of listenerClassName (in this case, DisplayLeakService) for processing
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
Copy the code
Here will create a heap profiler, for us a snapshot of the file is analyzed, then the result by the method of AbstractAnalysisResultService results to DisplayLeakService class for processing.
Detection of leak results
The main purpose of the HeapAnalyzer class is to detect memory leaks in the objects we monitor by analyzing.hprof files
//HeapAnalyzer.java
// Parse the hprof file into the corresponding AnalysisResult object
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if(! heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
// Start reading Dump file
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
// The.hprof parser, which is the haha library class
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
// Parse to generate a snapshot, which contains information about all referenced objects
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// According to the key value, check whether the snapshot has the required object
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
if (leakingRef == null) {
// Indicates that the object does not exist and was collected during gc. Indicates that there are no memory leaks
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
// Detect the path of the leak and return the detected result
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
Copy the code
This method uses the HAHA tripartite library to parse and process.hprof files. The main process is as follows:
- Create a buffer from the.hprof file to read the file
- The HprofParser is used to parse the hprof file and generate the Snapshot object. In this step, we build a reference relationship tree of the Object. We can query the information of each Object in this tree, including the Class information, memory address, held references, and the relationship of held references.
- Obtain the corresponding reference leakingRef in the Snapshot based on the key value of the incoming monitored object.
- Analysis leakingRef to obtain the path of the memory leak. A shortest reference path to the leak object will be found. This is accomplished by findLeakTrace, in fact, to find the shortest reference path logic is encapsulated in PathsFromGCRootsComputerImpl getNextShortestPath and processCurrentReferrefs method
Notification of a leak
When find our memory leak paths, invoked AbstractAnalysisResultService. SendResultToListener results to DisplayLeakService class for processing.
//AbstractAnalysisResultService.java
public static void sendResultToListener(@NonNull Context context,
@NonNull String listenerServiceClassName,
@NonNull HeapDump heapDump,
@NonNull AnalysisResult result) {
Class<? > listenerServiceClass;
try {
// Get a class information by reflection
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
// Save the result to a file and pass the file path to the service
File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);
if(analyzedHeapFile ! =null) {
intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());
}
// Start the service, and then pass the location of the result file of the memory leak analysis
ContextCompat.startForegroundService(context, intent);
}
Copy the code
A DisplayLeakService is started, passing along the file path information for the memory leak analysis results.
Then through onHandleIntent () – > onHandleIntentInForeground () – > onHeapAnalyzed (). The onHeapAnalyzed method of DisplayLeakService was eventually called
protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
HeapDump heapDump = analyzedHeap.heapDump;
AnalysisResult result = analyzedHeap.result;
// Generate a prompt String based on the leaked information
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
// Rename the.hprof file
heapDump = renameHeapdump(heapDump);
// Save the analysis results
boolean resultSaved = saveResult(heapDump, result);
// Result header
String contentTitle;
if (resultSaved) {
PendingIntent pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if(result.failure ! =null) {
// Analysis failed
contentTitle = getString(R.string.leak_canary_analysis_failed);
} else {
String className = classSimpleName(result.className);
if (result.leakFound) {// Memory leak detected
if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
if (result.excludedLeak) {// Excluded test results
contentTitle = getString(R.string.leak_canary_leak_excluded, className);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
}
} else {
String size = formatShortFileSize(this, result.retainedHeapSize);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
}
}
} else {
// No memory leak detected
contentTitle = getString(R.string.leak_canary_class_no_leak, className);
}
}
String contentText = getString(R.string.leak_canary_notification_message);
//*** * key method *** display a Notification
showNotification(pendingIntent, contentTitle, contentText);
} else {
onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
}
afterDefaultHandling(heapDump, result, leakInfo);
}
Copy the code
The function of this service is to inform users of the specific memory leak situation by notifying them of the information related to the leak path after analysis.
At this point, the entire implementation process parsing for LeakCanary is complete.
New knowledge learned
The whole study, or learned some things that did not realize before.
- Mainly through registerActivityLifecycleCallbacks to register the destruction of the Activity of listening in to us.
- Weak reference queues are used to monitor the Activity references that we have destroyed to see if they are recycled.
- For garbage collection, runtime.getruntime ().gc() is used.
- You can use CountDownLatch for synchronization between threads. For example, this set of source code for showToast processing.
- Different versions of Android may have some memory leaks of their own.
Source code: Leakcanary -source
This article is published by Kaiken!
Synchronous public number [open Ken]