最近接入了Matrix性能監(jiān)控看了下Matrix的Resource-Plugin,簡單的監(jiān)控了Activity的泄漏但是沒有做Fragment的泄漏和View的泄漏分析,但是對生成的堆快照文件做了一個shrink操作,將無用的部分刪除,但是由于結(jié)構(gòu)較為簡單所以功能缺少的比較多,比如需要手動的analyze hprof文件來查看泄漏發(fā)生在哪里,而沒有LeakCanary的比較優(yōu)秀的最短路徑計(jì)算方案,在手動分析hprof的過程中[依賴square:haha],發(fā)現(xiàn)計(jì)算堆中對象建立聯(lián)系的過程非常非常慢,因?yàn)樾枰獜腉C_ROOT依次遍歷引用,直到將所有對象都計(jì)算完畢為止,而Leakcanary找最短路徑卻十分的快,所以看一下Code的實(shí)現(xiàn)。
/**
* Creates a {@link RefWatcher} instance and makes it available through {@link
* LeakCanary#installedRefWatcher()}.
*
* Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
*
* @throws UnsupportedOperationException if called more than once per Android process.
*/
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
初始化的時候會先綁定給具體的ActivityRefWatcher和FragmentRefWatcher
先查看ActivityRefWatcher
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
通過綁定Activity生命周期來監(jiān)聽Activity是否泄漏
Fragment類似,依賴FramgentManager.FragmentLifecyclerCallbacks
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
來實(shí)現(xiàn),調(diào)用refWatcher.watch建立連接
refWatcher.watch的具體代碼如下:
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
入?yún)⒅恍枰P(guān)注第一個watchedReference,這邊傳入的只有三個參數(shù),Activity/Fragment/View
生成了一個KeyedWeakReference對象,這個對象是一個多了key【判斷引用】和name的弱引用
再次之后就會將生成的引用塞到一個一直運(yùn)行確保引用會被系統(tǒng)回收的隊(duì)列中
【生成的引用沒有對象持有他的強(qiáng)引用,所以一次正常的GC之后會被回收】
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
這段代碼首先會向一個WatchExector對象扔一個retryable,在Leakcanary中 WatchExector的實(shí)現(xiàn)類是AndroidWatchExecutor,execute的實(shí)現(xiàn)為
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
發(fā)送任務(wù)需要在主線程中執(zhí)行,再由
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
最終交給backgroundHandler執(zhí)行
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
再看一下Retryable的實(shí)現(xiàn)
@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);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
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.analyze(heapDump);
}
return DONE;
}
首先執(zhí)行的
private void removeWeaklyReachableReferences() {
// 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.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
這個方法的作用是什么呢?
【可能有偏差】
GC回收器回收垃圾前會先堆可被回收的對象進(jìn)行標(biāo)記,再依次執(zhí)行Object的finalize方法。
那么這個函數(shù)的目的就是將這些被標(biāo)記的對象,也是指向可以被回收的對象的弱引用從retainedKeys中移除,
因?yàn)闃?gòu)建引用的時候傳入的是refWatcher對象持有的ReferenceQueue對象,所以這里可以保證我們創(chuàng)建的所有KeyWeakReference對象都在對象持有的queue中。
另外在Debug的模式下,LeakCanary會執(zhí)行返回RETRY,也就是重試,而不會再往下執(zhí)行,備注也寫的很清楚,這是因?yàn)樵贒ebug模式下會出現(xiàn)錯誤的內(nèi)存泄漏。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
接著會檢查我們的keyedweakedReference是否還在retainedKeys中,因?yàn)槲覀儎倓傄呀?jīng)把可以被GC回收的對象的WeakReferecne排除掉了,如果不在的話說明沒有發(fā)生泄漏,返回DONE。表示這次execute已經(jīng)完成了,另提一下,返回RETRY則會增加一個attemp_count,在進(jìn)行方法體的執(zhí)行。
再接著, 由我們的gcTrigger執(zhí)行runGC()方法, gcTrigger的具體實(shí)現(xiàn)類在接口中
public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}
可以看到代碼很簡單,rungc的內(nèi)部就是先執(zhí)行g(shù)c。再給100ms交給gc回收器將可以回收的weakReferecnce添加到他們的queue中,再執(zhí)行Object的finalize方法。
在這之后我們在進(jìn)行removeWeaklyReachableReferences()并檢查,如果還存在,說明我們綁定的Object對象已經(jīng)發(fā)生了泄漏,并在發(fā)生泄漏之后進(jìn)行dumpfile,并標(biāo)明了泄漏的key和name。
隨后通知我們的listener進(jìn)行分析并返回Done。
listener的實(shí)現(xiàn)類為ServiceHeapDumpListener
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
交給了HeapAnalyzerService進(jìn)行analyze
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
而HeapAnalyzerService又對intent進(jìn)行了轉(zhuǎn)接,任務(wù)交給了后臺的服務(wù)去處理.
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
接受Intent的代碼如上,首先會新建一個HeapAnalyzer對象,參數(shù)的話第一個是需要排除的,使用過LeakCanary的應(yīng)該知道這是啥,最后一個也不用在意,現(xiàn)在會是一個空的list,下面就是重頭戲,checkLeak.
/**
* Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
* and then computes the shortest strong reference path from that instance to the GC roots.
*/
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 {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
首先listener更新progress的代碼可以忽略不看,幾個比較正常的分析HeapDumpFile的步驟大家可以看一下HaHa庫,也是Leakcanary作者的作品。
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
打開堆存儲文件并得到一個快照,在初始化之后可以通過snapshot.findclass("XXX")來找到某個類的文件,不過這個類和Java中的Class對象不一樣,他是自己生成的類,并且通過類可以得到他的所有實(shí)現(xiàn)類。
deduplicateGcRoots(snapshot);
再接著是對GC_Root進(jìn)行裁剪,因?yàn)镴VM是采用Gc_root的判斷方法,所以GC_root的多少決定了對堆快照分析的時間消耗多少。
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
方法內(nèi)部就是尋找到堆中所有keyedweakedReference實(shí)例檢索出來并且檢查key_field和我們傳入的參數(shù)是否一樣,如果一樣的話則返回他的reference_Instance(Activity, Fragment, View)等等
如果返回的為Null,則說明在dumpfile的過程中對象被回收了,這種也不算Leak,我們將通知一個Noleak消息回去,如果找到了則返回
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef, boolean computeRetainedSize) {
listener.onProgressUpdate(FINDING_SHORTEST_PATH);
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
long retainedSize;
if (computeRetainedSize) {
listener.onProgressUpdate(COMPUTING_DOMINATORS);
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
} else {
retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
首先創(chuàng)建了一個最短路徑Finder的對象,隨后將leakingRef和snapshot傳入對象進(jìn)行findpath,
Result findPath(Snapshot snapshot, Instance leakingRef) {
clearState();
canIgnoreStrings = !isString(leakingRef);
enqueueGcRoots(snapshot);
boolean excludingKnownLeaks = false;
LeakNode leakingNode = null;
while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
LeakNode node;
if (!toVisitQueue.isEmpty()) {
node = toVisitQueue.poll();
} else {
node = toVisitIfNoPathQueue.poll();
if (node.exclusion == null) {
throw new IllegalStateException("Expected node to have an exclusion " + node);
}
excludingKnownLeaks = true;
}
// Termination
if (node.instance == leakingRef) {
leakingNode = node;
break;
}
if (checkSeen(node)) {
continue;
}
if (node.instance instanceof RootObj) {
visitRootObj(node);
} else if (node.instance instanceof ClassObj) {
visitClassObj(node);
} else if (node.instance instanceof ClassInstance) {
visitClassInstance(node);
} else if (node.instance instanceof ArrayInstance) {
visitArrayInstance(node);
} else {
throw new IllegalStateException("Unexpected type for " + node.instance);
}
}
return new Result(leakingNode, excludingKnownLeaks);
}
進(jìn)行findpath之前,需要先對state進(jìn)行清除,這里的state指的就是在anayze過程中生成的緩存
switch (rootObj.getRootType()) {
case JAVA_LOCAL:
Instance thread = HahaSpy.allocatingThread(rootObj);
String threadName = threadName(thread);
Exclusion params = excludedRefs.threadNames.get(threadName);
if (params == null || !params.alwaysExclude) {
enqueue(params, null, rootObj, null);
}
break;
首先遍歷整個snapshot的GC_ROOTs列表,可以看到在對JAVA_LOCAL對象進(jìn)行判斷的時候我們會取出所在線程的Exclusion對象,alwaysExclude代表線程是一個不會被回收,也就是本身設(shè)計(jì)就是static 的概念,比如main線程。
如果不滿足,就會將參數(shù)和rootObj塞入隊(duì)列,另外還有下面的一些正常情況會塞入隊(duì)列
if (child == null) {
return;
}
if (isPrimitiveOrWrapperArray(child) || isPrimitiveWrapper(child)) {
return;
}
// Whether we want to visit now or later, we should skip if this is already to visit.
if (toVisitSet.contains(child)) {
return;
}
boolean visitNow = exclusion == null;
if (!visitNow && toVisitIfNoPathSet.contains(child)) {
return;
}
if (canIgnoreStrings && isString(child)) {
return;
}
if (visitedSet.contains(child)) {
return;
}
LeakNode childNode = new LeakNode(exclusion, child, parent, leakReference);
if (visitNow) {
toVisitSet.add(child);
toVisitQueue.add(childNode);
} else {
toVisitIfNoPathSet.add(child);
toVisitIfNoPathQueue.add(childNode);
}
首先查看傳入的需要進(jìn)行判斷的對象是否為空,或者是否是基本包裝類型對象或者其數(shù)組對象,如果滿足就返回。
緊接著判斷是否在即將被訪問的集合中,如果在就返回,防止重復(fù)計(jì)算。如果String類型可以忽略且為String類型,則返回,如果已經(jīng)計(jì)算過,則返回。
然后就會創(chuàng)建一個childNode加入即將訪問的隊(duì)列中,第一輪下來之后所有的GC_ROOT都會加入這個隊(duì)列中。
緊接著會對toVisitQueue進(jìn)行循環(huán)尋找是否與我們的leakRef一樣直到找到為止
if (node.instance instanceof RootObj) {
visitRootObj(node);
} else if (node.instance instanceof ClassObj) {
visitClassObj(node);
} else if (node.instance instanceof ClassInstance) {
visitClassInstance(node);
} else if (node.instance instanceof ArrayInstance) {
visitArrayInstance(node);
} else {
throw new IllegalStateException("Unexpected type for " + node.instance);
}
這些Code會遞歸調(diào)用enqueue方法將待檢查的對象放到我們的隊(duì)列中,找到就結(jié)束
最后生成一個instance leak tree,并通知用戶