前言
提到Java語言的特色,不管是教科書仍是程序員通常都會羅列出面向對象、可移植性及安全等特色。但若是你是一位剛從C/C++轉到Java的程序員,對Java語言的特性除了面向對象以外,最外直接的應當是在Java虛擬機(JVM)在內存管理方面給咱們變成帶來的便利。JVM的這一大特性使Java程序員從繁瑣的內存管理工做中獲得了必定解放,可是JVM的這個特色的實現也是有代價的,而且它也並不是萬能。所以若是一個編程習慣很差的Java程序員若是徹底將內存回收寄但願於JVM,那麼OOM(Out Of Memory)就已經悄悄潛伏在了他的程序之中。
Android應用基於Java實現,所以它也將Java的優缺點繼承了過來。相對來講,移動設備對於內存問題更爲敏感,程序在申請必定的內存但又沒有及時獲得釋放後就很容易發生OOM而致使crash。所以Android程序員開發過程當中通常都會定時排查本身程序中可能出現的這些雷點,儘量地避免由於crash問題而影響用戶體驗。
簡介
目前Java程序最經常使用的內存分析工具應該是MAT(Memory Analyzer Tool),它是一個Eclipse插件,同時也有單獨的RCP客戶端,也能夠經過官網的SVN下載到它的源碼並編譯成jar包。LeakCanary本質上就是一個基於MAT進行Android應用程序內存泄漏自動化檢測的的開源工具,經過集成這個工具代碼到本身的Android工程當中就可以在程序調試開發過程當中經過一個漂亮的界面(以下圖)隨時發現和定位內存泄漏問題,而不用每次在開發流程中都抽出專人來進行內存泄漏問題檢測,極大地方便了Android應用程序的開發。源碼地址
這裏寫圖片描述
假如你如今想集成LeakCanary到本身的工程中,那麼你只須要作如下工做:
在gradle文件中引入依賴:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
或
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
在項目的Application 類中添加
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
圖解
此章先自頂向下,用圖簡化,不落代碼陷阱,不入細節迷障。理清思路以後,咱們再踏入源碼的海洋,這樣理解會更加清晰。
問:有沒有一種簡單直接且能有效定位內存泄漏位置的方法呢?
答:有,那就是 LeakCanary 。咱們能夠簡單地人爲:將一個 App 做爲輸入,經過 LeakCanary 檢測後,就會獲得內存泄漏位置結果(若是存在的話)。
這裏寫圖片描述
知其然知其因此然,LeakCanary 如此強大實用,那麼:LeakCanary 是怎麼實現的?
Android 應用的整個生命週期由其組件的生命週期組成,以下圖中所示。用戶使用應用的過程當中,在不一樣界面之間跳轉,每一個界面都經歷着」生死「的轉換,可在此創建檢測點。Activity/Fragment 都有 onDestory() 回調方法, 進入此方法後,Activity/Fragment生命週期結束,應該被回收。
這裏寫圖片描述
而後咱們須要解決:如何獲得未被回收的對象。ReferenceQueue+WeakReference+手動調用 GC可實現這個需求。
WeakReference 建立時,傳入一個 ReferenceQueue 對象。
當被WeakReference 引用的對象的生命週期結束,一旦被 GC 檢查到,GC 將會把該對象添加到 ReferenceQueue 中,待ReferenceQueue處理。
當 GC 事後對象一直不被加入 ReferenceQueue,它可能存在內存泄漏。
找到了未被回收的對象,如何確認是否真的內存泄漏?這裏能夠將問題轉換爲:未被回收的對象,是否被其餘對象引用?找出其最短引用鏈。VMDebug + HAHA 完成需求。
VM 會有堆內各個對象的引用狀況,並能以hprof文件導出。
HAHA 是一個由 square 開源的 Android 堆分析庫,分析 hprof 文件生成Snapshot對象。Snapshot用以查詢對象的最短引用鏈。
這裏寫圖片描述
找到最短引用鏈後,定位問題,排查代碼將會事半功倍。
以下泳道圖分析, LeakCanary 各個模塊如何配合達到檢測目的。
這裏寫圖片描述
深刻源碼分析
檢測原理
監聽
在Android中,當一個Activity走完onDestroy生命週期後,說明該頁面已經被銷燬了,應該被系統GC回收。經過Application.registerActivityLifecycleCallbacks()方法註冊Activity生命週期的監聽,每當一個Activity頁面銷燬時候,獲取到這個Activity去檢測這個Activity是否真的被系統GC。
檢測
當獲取了待分析的對象後,須要肯定這個對象是否產生了內存泄漏。
經過WeakReference + ReferenceQueue來判斷對象是否被系統GC回收,WeakReference 建立時,能夠傳入一個 ReferenceQueue 對象。當被 WeakReference 引用的對象的生命週期結束,一旦被 GC 檢查到,GC 將會把該對象添加到 ReferenceQueue 中,待ReferenceQueue處理。當 GC 事後對象一直不被加入 ReferenceQueue,它可能存在內存泄漏。
當咱們初步肯定待分析對象未被GC回收時候,手動觸發GC,二次確認。
分析
分析這塊使用了Square的另外一個開源庫haha,利用它獲取當前內存中的heap堆信息的快照snapshot,而後經過待分析對象去snapshot裏面去查找強引用關係。
源碼
監聽
直接從LeakCanary.install()方法開始看。
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
/**
* Builder to create a customized {@link RefWatcher} with appropriate Android defaults.
*/
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
install()方法中首先實例化了一個AndroidRefWatcherBuilder類。
而後使用listenerServiceClass()方法設置了DisplayLeakService類,這個類用於分析內存泄漏結果信息,而後發送通知給用戶。
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
...
/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
...
}
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
...
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
...
}
而後調用excludedRefs()方法設置添加一些白名單,通俗來說就是不要誤傷了友軍。在AndroidExcludedRefs類中以枚舉的形式定義了忽略列表信息,若是這些列表中的類發生了內存泄漏,並不會顯示出來,同時HeapAnalyzer在計算到GC roots的強引用路徑,也會忽略這些類。若是你想本身的某個類泄漏了,LeakCanary不提示,就加到這個類中。
public enum AndroidExcludedRefs {
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(VERSION.SDK_INT >= 19 && VERSION.SDK_INT <= 21) {
void add(Builder excluded) {
excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle").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.");
}
},
SPAN_CONTROLLER(VERSION.SDK_INT <= 19) {
void add(Builder excluded) {
String reason = "Editor inserts a special span, which has a reference to the EditText. That span is a NoCopySpan, which makes sure it gets dropped when creating a new SpannableStringBuilder from a given CharSequence. TextView.onSaveInstanceState() does a copy of its mText before saving it in the bundle. Prior to KitKat, that copy was done using the SpannableString constructor, instead of SpannableStringBuilder. The SpannableString constructor does not drop NoCopySpan spans. So we end up with a saved state that holds a reference to the textview and therefore the entire view hierarchy & activity context. Fix: https://github.com/android/platform_frameworks_base/commit/af7dcdf35a37d7a7dbaad7d9869c1c91bce2272b . To fix this, you could override TextView.onSaveInstanceState(), and then use reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.";
excluded.instanceField("android.widget.Editor$EasyEditSpanController", "this$0").reason(reason);
excluded.instanceField("android.widget.Editor$SpanController", "this$0").reason(reason);
}
},
...
}
最後調用了buildAndInstall()方法,建立了一個RefWatcher對象並返回了,這個對象用於檢測是否有對象未被回收致使內存泄漏,後續會詳細講解這塊。
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
由於分析泄漏信息是在另外一個進程,若是判斷出當前Application啓動是在分析泄漏信息的進程中,就返回DISABLED,不去執行後續的初始化操做。在這裏LeakCanary提供了一個很好的方式去區分啓動是否在應用主進程,能夠做爲參考。
若是發現是在應用主進程中,就會進行一些初始化操做。
LeakCanary.enableDisplayLeakActivity(context);這個是調用PackageManager將DisplayLeakActivity設置爲可用。
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
從配置文件中能夠看見LeakCanary的這幾個服務都是在新的進程中運行的,DisplayLeakActivity默認是不可用android:enabled=」false」,這樣才能在一開始未存在內存泄漏時候,隱藏LeakCanary圖標的。
<application>
<service
android:name=".internal.HeapAnalyzerService"
android:process=":leakcanary"
android:enabled="false"/>
<service
android:name=".DisplayLeakService"
android:process=":leakcanary"
android:enabled="false"/>
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@drawable/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:theme="@style/leak_canary_Theme.Transparent"
android:name=".internal.RequestStoragePermissionActivity"
android:process=":leakcanary"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
android:enabled="false"
android:excludeFromRecents="true"
android:icon="@drawable/leak_canary_icon"
android:label="@string/leak_canary_storage_permission_activity_label"/>
</application>
緊接着執行了ActivityRefWatcher.install((Application) context, refWatcher);,這裏將實例化的RefWatcher當作入參傳入,同時對Activity的生命週期進行了監聽。
public static void install(Application application, RefWatcher refWatcher) {
(new ActivityRefWatcher(application, refWatcher)).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
void onActivityDestroyed(Activity activity) {
this.refWatcher.watch(activity);
}
private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
public void onActivityStarted(Activity activity) {
}
public void onActivityResumed(Activity activity) {
}
public void onActivityPaused(Activity activity) {
}
public void onActivityStopped(Activity activity) {
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
能夠看到爲了保證不初始化兩次監聽,先移除了一次,而後再次添加了監聽。每次當Activity執行完onDestroy生命週期,LeakCanary就會獲取到這個Activity,而後對它進行分析,查看是否存在內存泄漏。
RefWatcher類中有一些成員變量,解釋一下它們的做用。
watchExecutor 確保分析任務操做是在主線程進行的,同時默認延遲5秒執行分析任務,留時間給系統GC
debuggerControl debug控制中心
gcTrigger 內部調用Runtime.getRuntime().gc(),手動觸發系統GC
heapDumper 用於建立.hprof文件,用於儲存heap堆的快照,能夠獲知程序的哪些部分正在使用大部分的內存
heapDumpListener 分析結果完成後,會告訴這個監聽者
excludedRefs 白名單,分析內存泄漏忽略的名單
public final class RefWatcher {
public static final RefWatcher DISABLED = (new RefWatcherBuilder()).build();
private final WatchExecutor watchExecutor;
private final DebuggerControl debuggerControl;
private final GcTrigger gcTrigger;
private final HeapDumper heapDumper;
private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue;
private final Listener heapdumpListener;
private final ExcludedRefs excludedRefs;
...
}
判斷是否存在內存泄漏
從上面Activity生命週期監聽回調能夠看見,每次當Activity執行完onDestroy生命週期,會調用RefWatcher去分析是否存在內存泄漏。
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* 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);
}
checkNotNull()方法能夠不用管,用來判斷對象是否爲null。在這裏聲明瞭一個弱引用,將Activity放入,而後調用了ensureGoneAsync()方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
先生成一個隨機數用做key放在retainedKeys容器裏面,用來區分待分析對象是否被回收,而後使用watchExecutor去調度分析任務,主要有兩點,一保證分析是在主線程進行,二延遲五秒分析內存泄漏,給系統GC時間。這部分有興趣能夠深刻去看一下,與分析的主流程關係不大,咱們接下繼續看。
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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
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);
}
}
private boolean gone(KeyedWeakReference reference) {
return !this.retainedKeys.contains(reference.key);
}
在這裏首先執行removeWeaklyReachableReferences()嘗試着從弱引用的隊列中獲取待分析對象,若是不爲空,那麼說明已經被系統回收了,就將retainedKeys中對應的key去掉。若是被系統回收了,直接就返回DONE;若是沒有被系統回收,可能存在內存泄漏,爲了保證結果的準確性,調用gcTrigger.runGc();,手動觸發系統GC,而後再嘗試移除待分析對象,若是還存在,說明存在內存泄漏。
public void runGc() {
Runtime.getRuntime().gc();
this.enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
try {
Thread.sleep(100L);
} catch (InterruptedException var2) {
throw new AssertionError();
}
}
手動觸發系統GC後,enqueueReference()方法經過沉睡100毫秒給系統GC時間,System.runFinalization()是強制調用已經失去引用的對象的finalize方法。
肯定有內存泄漏後,調用heapDumper.dumpHeap()生成.hprof文件目錄,而後回調heapdumpListener監聽,這個監聽者具體實現是ServiceHeapDumpListener類,會回調到analyze()方法。
public void analyze(HeapDump heapDump) {
Preconditions.checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
}
HeapDump是個model類,裏面用於儲存一些分析類強引用路徑的須要的信息。HeapAnalyzerService.runAnalysis方法是發送了一個Intent,啓動了HeapAnalyzerService服務,這個服務是IntentService。
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass){
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra("listener_class_extra", listenerServiceClass.getName());
intent.putExtra("heapdump_extra", heapDump);
context.startService(intent);
}
啓動Service後,會在onHandleIntent分析,找到內存泄漏對象的引用關係。
protected void onHandleIntent(Intent intent) {
if(intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.", new Object[0]);
} else {
String listenerClassName = intent.getStringExtra("listener_class_extra");
HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
找到引用關係
啓動分析泄漏的服務後,會實例化一個HeapAnalyzer對象,而後調用它的checkForLeak()方法來分析最終獲得結果。
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if(!heapDumpFile.exists()) {
IllegalArgumentException e1 = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return AnalysisResult.failure(e1, this.since(analysisStartNanoTime));
} else {
try {
MemoryMappedFileBuffer e = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(e);
Snapshot snapshot = parser.parse();
this.deduplicateGcRoots(snapshot);
Instance leakingRef = this.findLeakingReference(referenceKey, snapshot);
return leakingRef == null?AnalysisResult.noLeak(this.since(analysisStartNanoTime)):this.findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable var9) {
return AnalysisResult.failure(var9, this.since(analysisStartNanoTime));
}
}
}
這裏用到了Square的另外一個庫haha,哈哈哈哈哈,名字真的就是叫這個,開源地址
首先用HprofParser類獲取到內存中的heap堆快照,而後對調用deduplicateGcRoots()對快照作了去重處理,去除一些重複的強引用關係。findLeakingReference()方法就是拿到待分析的類,去快照裏面找引用關係,並將結果返回。
HeapAnalyzerService服務拿到分析結果後,調用了AbstractAnalysisResultService.sendResultToListener()方法,這個方法啓動了另外一個服務。
public static void sendResultToListener(Context context, String listenerServiceClassName, HeapDump heapDump, AnalysisResult result) {
Class listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException var6) {
throw new RuntimeException(var6);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra("heap_dump_extra", heapDump);
intent.putExtra("result_extra", result);
context.startService(intent);
}
listenerServiceClassName就是開始LeakCanary.install方法傳入的DisplayLeakService類,它自己也是個IntentService服務。
protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heap_dump_extra");
AnalysisResult result = (AnalysisResult)intent.getSerializableExtra("result_extra");
try {
this.onHeapAnalyzed(heapDump, result);
} finally {
heapDump.heapDumpFile.delete();
}
}
服務啓動後,會調用自身的onHeapAnalyzed方法
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", new Object[]{leakInfo});
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if(shouldSaveResult) {
heapDump = this.renameHeapdump(heapDump);
resultSaved = this.saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
// 設置消息通知的 pendingIntent/contentTitle/contentText
...
int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
this.afterDefaultHandling(heapDump, result, leakInfo);
}
在這個方法中對判斷是否須要將內存泄漏信息存到本地,若是須要就存到本地,而後設置消息通知的基本信息,經過LeakCanaryInternals.showNotification方法調用系統自身的通知欄通知,告訴用戶應用有內存泄漏。來個圖:
這裏寫圖片描述
至此全部LeakCanary的檢測過程經過源碼的形式都梳理了一遍。
總結
它的基本工做原理以下:
RefWatcher.watch() 建立一個 KeyedWeakReference 到要被監控的對象。
而後在後臺線程檢查引用是否被清除,若是沒有,調用GC。
若是引用仍是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。
在另一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件。
得益於惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄漏。
HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並肯定是不是泄漏。若是是的話,創建致使泄漏的引用鏈。
引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展現出來。
總的來講,LeakCanary有以下幾個明顯優勢:
針對Android Activity組件徹底自動化的內存泄漏檢查。
可定製一些行爲(dump文件和leaktrace對象的數量、自定義例外、分析結果的自定義處理等)。
集成到本身工程並使用的成本很低。
友好的界面展現和通知
https://blog.csdn.net/import_sadaharu/article/details/81407728
android