為什麼使用LeakCanary檢測記憶體洩漏?
為什麼要使用LeakCanary?
不再需要的物件依然被引用,導致物件被分配的記憶體無法被回收,這就是記憶體洩漏的原因。
例如:一個Activity例項物件在呼叫了onDestory方法後是不再被需要的,如果儲存了一個引用Activity物件的靜態域,將導致Activity無法被垃圾回收器回收。
LeakCanary標識一個需要更長時間的物件,並找到防止其被垃圾收集的引用鏈。
引用鏈來自於垃圾回收器的**可達性分析演算法:當一個物件到GC Roots 沒有任何引用鏈相連時,則證明此物件時不可用的。**如圖: 物件object5、object6、object7 雖然互相有關聯,但是它們到 GC Roots 是不可達的,所以它們將會被判定為是可回收的物件。
在Java語言中,可作為GC Roots的物件包括下面幾種:
- 虛擬機器棧(棧幀中的本地變量表)中引用的物件。
- 方法區中靜態屬性引用的物件。
- 方法區中常量引用的物件。
- 本地方法棧中JNI(即一般說的Native方法)引用的e物件。
LeakCanary是怎麼工作的?
- RefWatcher.watch() 建立了一個 KeyedWeakReference 來監視物件.
- 稍後,在後臺執行緒中,檢查引用是否已被清除,如果沒有,則觸發GC。
- 如果引用一直沒有被清除,它會dumps the heap 到一個.hprof 檔案中,然後將.hprof 檔案儲存到檔案系統。
- HeapAnalyzerService在單獨的程序中啟動,HeapAnalyzer使用
- HeapAnalyzer 通過唯一的引用key來找到heap dump 中的KeyedWeakReference,並定位記憶體洩漏引用。
- HeapAnalyzer計算到GC Roots的最短強引用鏈路徑來確定是否有洩漏,然後構建導致洩漏的引用鏈。
- 然後將結果返回到應用程式程序中的DisplayLeakService,並顯示洩漏通知。
原始碼分析
初始化
程式碼從LeakCanary.install(this)方法開始,它主要是建立一個RefWatcher物件,然後開始監視activity或fragment物件。install方法程式碼如下:
/** * Creates a {@link RefWatcher} that works out of the box, and starts watching activity * references (on ICS+). */ public static @NonNull RefWatcher install(@NonNull Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
- refWatcher(application):建立了一個AndroidRefWatcherBuilder類物件,AndroidRefWatcherBuilder類主要是設定一些預設資訊。
- listenerServiceClass(DisplayLeakService.class):設定監聽分析結果的監聽器。當記憶體洩漏結果分析完成,會呼叫DisplayLeakService監聽器的onHeapAnalyzed()方法。
- excludedRefs(AndroidExcludedRefs.createAppDefaults().build()):過濾掉一些由於SDK版本和製造廠商本身引起的記憶體洩漏問題。AndroidExcludedRefs是一個列舉類,它列舉了所遇到的記憶體洩漏問題。例如:在SDK版本19到21之間,ActivityClientRecord類中的nextIdle域變數會保持引用已經呼叫onDestroy()方法的Activity物件,導致記憶體洩漏。
- buildAndInstall():開始監視Activity和Fragment物件。
buildAndInstall()方法中的操作
在 buildAndInstall() 方法中:
/**
1. Creates a {@link RefWatcher} instance and makes it available through {@link
2. LeakCanary#installedRefWatcher()}.
3. 4. Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
4. 6. @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;
}
1.RefWatcher類
RefWatcher refWatcher = build(); 其實等於 RefWatcher refWatcher = new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,heapDumpBuilder);下面為RefWatcher的構造方法:
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
HeapDumper heapDumper, HeapDump.Listener heapdumpListener, HeapDump.Builder heapDumpBuilder) {
this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
this.heapDumper = checkNotNull(heapDumper, "heapDumper");
this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
this.heapDumpBuilder = heapDumpBuilder;
retainedKeys = new CopyOnWriteArraySet<>();
queue = new ReferenceQueue<>();
}
- watchExecutor:WatchExecutor介面的實現類是AndroidWatchExecutor,AndroidWatchExecutor 類用於監視Android 物件洩漏,它等待主執行緒成變成空閒時,然後再延遲一個指定的時間傳送到後臺執行緒中去執行。
- debuggerControl:用於檢測當前是否正在除錯中,如果是則不會執行記憶體洩露檢測。
- gcTrigger:當一個被監視的物件預期弱可到達,但是尚未新增到引用佇列中時呼叫。這給應用程式提供了一個hook,以便在再次檢查引用佇列之前執行GC,以避免在可能的情況下進行 heap dump。
- heapDumper:用於dump heap,然後儲存到檔案中。
- heapdumpListener:用於分析dump heap後產生的檔案。
- heapDumpBuilder:dump heap需要的一些引數資訊。
- retainedKeys:用於儲存監視物件對應的key。
- queue:用於監聽被監視的物件是否已經被垃圾回收器回收。
2.DisplayLeakActivity元件
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
public static void setEnabledAsync(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
在子執行緒中去告訴系統不要關閉DisplayLeakActivity元件,DisplayLeakActivity其實就是我們見到的下面這個頁面:
3.開始監視Activity和Fragment物件
if (watchActivities) {//watchActivities預設為true
ActivityRefWatcher.install(context, refWatcher);//監視Activity物件
}
if (watchFragments) {//watchFragments預設為true
FragmentRefWatcher.Helper.install(context, refWatcher);//監視Fragment物件
}
監視Activity物件時,首先用Application物件註冊Activity的生命週期回撥監聽器,然後在Activity的onDestroy方法中呼叫:
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);//註冊Activity的生命週期回撥監聽器
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);//監視 Activity物件
}
};
監視Fragment時,首先用Application物件註冊Activity的生命週期回撥監聽器。在Activity的onCreate方法呼叫時,用FragmentManager物件註冊Fragment的生命週期回撥監聽器。當Fragment的onDestroyView方法呼叫時,開始監視 Fragment的View物件;在Fragment的onDestroy方法呼叫時,開始監視 Fragment物件。
註冊Fragment的生命週期回撥監聽器:
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
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 (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
這裡提供了SupportFragmentRefWatcher或AndroidOFragmentRefWatcher類來監聽Fragment的生命週期。SupportFragmentRefWatcher支援support包,AndroidOFragmentRefWatcher支援系統版本O以上。
下面為SupportFragmentRefWatcher類中註冊Fragment的生命週期回撥監聽器:
class SupportFragmentRefWatcher implements FragmentRefWatcher {
private final RefWatcher refWatcher;
SupportFragmentRefWatcher(RefWatcher refWatcher) {
this.refWatcher = refWatcher;
}
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);//監視 View物件
}
}
@Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);//監視 Fragment物件
}
};
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
}
在AndroidOFragmentRefWatcher也類似。
開始分析記憶體洩漏
在開始分析記憶體洩漏之前,先了解下弱引用(WeakReference)和引用佇列( ReferenceQueue):
- 弱引用(WeakReference):弱引用物件,它們並不禁止其指示物件變得可終結,並被終結,然後被回收。弱引用最常用於實現規範化的對映。
- 引用佇列( ReferenceQueue):引用佇列,在檢測到適當的可到達性更改後,垃圾回收器將已註冊的引用物件新增到該佇列中。
建立一個弱引用物件:
public class WeakReference<T> extends Reference<T> {
//建立引用給定物件的新的弱引用。
public WeakReference(T referent) {
super(referent);
}
//建立引用給定物件的新的弱引用,並向給定佇列註冊該引用。
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
當Activity或者Fragment在呼叫onDestroy方法時,就會開始分析Activity或者Fragment物件是否會存在記憶體洩漏。
記憶體洩漏分析從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();//生成一個唯一的key,用於查詢KeyedWeakReference
retainedKeys.add(key);//快取key
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//包裝物件成弱引用,然後新增到ReferenceQueue中
ensureGoneAsync(watchStartNanoTime, reference);//放到後臺執行緒中去執行
}
// 延遲指定的時間後傳送到後臺執行緒中去執行
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);//放到後臺執行緒中去執行
}
});
}
@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();////如果物件沒有被垃圾回收,就手動觸發gc
removeWeaklyReachableReferences();//再次移除弱可達的引用物件
if (!gone(reference)) {//再次檢查物件是否從引用佇列中移除了,如果是就沒有記憶體洩漏,正常結束,否則繼續往下執行,開始dump heap
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;
}
從上面看到,呼叫關係依次是:
- RefWatcher → watch → ensureGoneAsync → (AndroidWatchExecutor → execute) → ensureGone →
- ServiceHeapDumpListener → analyze →
- HeapAnalyzerService → runAnalysis → onHandleIntentInForeground →
- AbstractAnalysisResultService → sendResultToListener → onHandleIntentInForeground → (DisplayLeakService) → onHeapAnalyzed → showNotification
- LeakCanaryInternals → showNotification → DisplayLeakActivity
第一步
首先將物件包裝成KeyedWeakReference,然後等待主執行緒空閒時,再延遲一個指定的時間傳送到後臺執行緒中執行。接下來是移除弱可達的物件:
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);
}
}
引用佇列的poll()方法的意思是:輪詢此佇列,檢視是否存在可用的引用物件。如果存在一個立即可用的物件,則從該佇列中移除此物件並返回。否則此方法立即返回 null。
其次,再檢查當前監視的物件是否從引用佇列中移除了,如果是就代表沒有記憶體洩漏,停止執行;否則繼續向下執行,開始手動gc:
gcTrigger.runGc();
public void runGc() {
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
當手動gc完成後,又重新移除弱可達的物件,檢查當前監視的物件是否從引用佇列中移除。如果物件還沒有移除,就開始dump heap進行分析。
第二步
在dump heap之前,還要先做一些準備工作,建立儲存檔案,以及要分析的記憶體洩漏物件的基本資訊。接下來呼叫heapdumpListener的analyze方法開始分析:
heapdumpListener.analyze(heapDump);
heapdumpListener就是在LeakCanary的install方法中listenerServiceClass(DisplayLeakService.class)賦的值:
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
接下呼叫ServiceHeapDumpListener類的analyze方法:
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
在analyze方法中又交給HeapAnalyzerService類的runAnalysis方法:
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);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
}
HeapAnalyzerService是一個IntentService,所以此時執行在後臺服務中,接下來呼叫HeapAnalyzerService的是onHandleIntentInForeground方法:
@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);
}
在這個方法中,呼叫HeapAnalyzer的checkForLeak方法開始分析記憶體洩漏:
/**
1. Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
2. 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));
}
}
HeapAnalyzer就是最開始說的:
- HeapAnalyzer使用HAHA來解析dump heap。
- HeapAnalyzer 通過唯一的引用key來找到heap dump 中的KeyedWeakReference,並定位記憶體洩漏引用。
- HeapAnalyzer計算到GC Roots的最短強引用鏈路徑來確定是否有洩漏,然後構建導致洩漏的引用鏈。
分析完成之後,將分析結果封裝成AnalysisResult物件。最後交給AbstractAnalysisResultService的子類DisplayLeakService來處理結果,DisplayLeakService主要用來顯示通知,展示在使用者面前。就此,記憶體洩漏分析完成。
總結
LeakCanary用到的最主要的知識點就是垃圾回收器的可達性分析演算法:當一個物件到GC Roots 沒有任何引用鏈相連時,則證明此物件時不可用的。
要明白LeakCanary的記憶體洩漏檢測原理,需要了解掌握:
- Activity和Fragment的生命週期回撥
- 弱引用和引用佇列
- 垃圾回收演算法:可達性分析演算法
總之,LeakCanary是一個非常實用的檢測App中記憶體洩漏的工具,我們可以通過它來避免記憶體洩漏,讓應用程式更加的完美。