在一個Activity執行完onDestroy()以後,將它放入WeakReference中,而後將這個WeakReference類型的Activity對象與ReferenceQueque關聯。這時再從ReferenceQueque中查看是否有沒有該對象,若是沒有,執行gc,再次查看,仍是沒有的話則判斷髮生內存泄露了。最後用HAHA這個開源庫去分析dump以後的heap內存java
(1) 添加app.gradle依賴 :android
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-fragment:1.6.3'
複製代碼
(2)初始化LeakCanary,在Application中添加:markdown
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
// 判斷當前進程是否爲LeakCanary進程,該進程運行一個HeapAnalyzerService服務
// 若是不是,則初始化LeakCanary進程
if (! LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
// Normal app init code...
}
}
複製代碼
注意:除了activities和fragments外,LeakCanary支持監聽應用中的任何對象,假如這個對象再也不使用到的話,經過執行下列代碼實現對某個對象的監聽。代碼以下:app
RefWatcher.watch(myDetachedView)
複製代碼
LeakCanary的入口方法是LeanCanary$install
方法,該方法源碼以下:dom
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application) // 建立一個AndroidRefWatcherBuilder對象
.listenerServiceClass(DisplayLeakService.class) // 註釋1
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 註釋2
.buildAndInstall(); // 註釋2
}
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
1234567891011121314
複製代碼
install方法目的就是建立並返回一個RefWatcher對象
,這個RefWatcher是LeakCanary的核心類,經過建造者模式構建。其中,listenerServiceClass
方法傳入了展現分析結果的Service(DisplayLeakService);excludedRefs
方法排除開發中能夠忽略的泄漏路徑;buildAndInstall
是主要函數,實現對activity的釋放監聽。接下來,咱們直接看buildAndInstall
方法源碼:ide
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// AndroidRefWatcherBuilder.java
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should + "only be called once."); } // 構建一個RefWacher對象 RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { if (enableDisplayLeakActivity) { LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true); } // 監聽全部Activities if (watchActivities) { ActivityRefWatcher.install(context, refWatcher); } // 監聽全部fragments if (watchFragments) { FragmentRefWatcher.Helper.install(context, refWatcher); } } LeakCanaryInternals.installedRefWatcher = refWatcher; return refWatcher; } 1234567891011121314151617181920212223242526 複製代碼
從上述源碼可知,它首先會調用AndroidRefWatcherBuilder的build方法構建一個RefWatcher實例,而後分別調用ActivityRefWatcher.install
方法和FragmentRefWatcher.Helper.install
方法實現對全部activities和fragments的釋放監聽。函數
下面咱們就以分析如何監聽activity爲例:工具
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
// 獲取應用的application
Application application = (Application) context.getApplicationContext();
// 實例化一個ActivityRefWatcher對象
ActivityRefWatcher activityRefWatcher =
new ActivityRefWatcher(application, refWatcher);
// 調用registerActivityLifecycleCallbacks來監聽Activity的生命週期
application.registerActivityLifecycleCallbacks(activityRefWatcher.
lifecycleCallbacks);
}
123456789101112
複製代碼
從install方法源碼能夠看出,LeakCanary主要經過調用Application的registerActivityLifecycleCallbacks方法實現對activity釋放(銷燬)監聽,該方法主要用來統一管理全部activity的生命週期。全部Activity在銷燬時都會回調ActivityLifecycleCallbacks的onActivityDestroyed
方法,也就是說,LeakCanary是在Activity的onDestory方法中實施監聽的,經過調用RefWatcher.watch
方法實現。源碼以下:oop
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
// 在Activity銷燬時,監控當前activity
// 傳入的是activity的引用
refWatcher.watch(activity);
}
};
複製代碼
接下來,咱們來看LeakCanary是如何監聽activity是否發生泄漏。RefWatcher.watch源碼:gradle
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
public void watch(Object watchedReference) {
// watchedReference爲被監視的activity引用
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// 自動生成一個主鍵,做爲全局惟一標識符
// 並插入到retainedKeys集合中
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// 1. 將activity的引用包裝到KeyedWeakReference中
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 2. 檢測是否發生泄漏
ensureGoneAsync(watchStartNanoTime, reference);
}
123456789101112131415161718192021222324
複製代碼
在RefWatcher.watch方法中,完成如下兩件事情:
首先,將當前被監控的activity引用、自動生成的key和一個ReferenceQueue 對象包裝到一個KeyedWeakReference
對象中,該對象繼承於WeakReference(弱引用)
。監測機制利用了Java的WeakReference和ReferenceQueue,經過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity對象若是可以被回收,則說明引用可達,垃圾回收器就會將該WeakReference引用(包含被監控的activity
)放到ReferenceQueue中,經過監測ReferenceQueue裏面的內容就能檢查到Activity是否可以被回收。KeyedWeakReference類源碼以下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \KeyedWeakReference.java
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
private final ReferenceQueue<Object> queue;
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue,
"referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
123456789101112131415161718
複製代碼
其次,檢測當前被監控的activity是否發生了泄漏,經過調用RefWatcher#ensureGoneAsync
方法實現,該方法又調用了RefWatcher#ensureGone
。相關源碼以下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
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);
// 1. 肯定是否存在內存泄漏
// (1)判斷當前activity的引用是否存在ReferenceQueue中,
// 若是存在,則說明引用可達,可以被GC回收,同時將其key從retainedKeys集合中刪除
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// (2)肯定retainedKeys集合中是否存在該activity對應的key
// 若是不存在了,說明該對象已經被回收,直接返回
if (gone(reference)) {
return DONE;
}
// (3)若是存在,先觸發一下GC操做,再嘗試判斷該activity的對象引用
// 是否保存到了ReferenceQueue
gcTrigger.runGc();
removeWeaklyReachableReferences();
// 2. 再次肯定retainedKeys集合中是否存在該activity對應的key
// 若是仍然存在,則說明發生了內存泄漏.生成堆內存快照,分析快照
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// (1) 建立heapDump文件,還沒寫入
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime()
- startDumpHeap);
//(2)建立HeapDump對象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
.referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//(3)調用heapdumpListener分析
// 調用HeapAnalyzerService的analyze實現,即後臺執行分析任務
heapdumpListener.analyze(heapDump);
}
return DONE;
}
複製代碼
從ensureGone方法源碼可知,它首先會去肯定被監控的activity對象是否發生了內存泄漏,而後若是肯定了確實發生了泄漏,就會dump內存快照並對快照進行分析,最終獲得泄漏的具體信息。接下來,咱們將對這三個方面進行詳細分析:
(1)肯定是否存在內存泄漏
肯定被監控的activity對象是否存在內存泄漏,主要是經過調用removeWeaklyReachableReferences
、gone
方法實現的,具體策略爲:首先,調用removeWeaklyReachableReferences
判斷當前被監控的activity對象的引用是否存在ReferenceQueue
中,若是存在說明,該activity對象引用可達,可以被GC回收,此時就將其key從retainedKeys
這個Set集合中移除;而後,調用gone方法肯定當前被監控的activity對象的引用是否存在retainedKeys
集合,若是不存在,說明該activity對象已經被回收,直接返回。可是,若是仍然存在,爲了肯定GC延遲或誤判,手動觸發一下GC操做,而後再進行一次上面的斷定操做,若是Gone方法仍然返回false,則說明被監控的activity對象發生了內存泄漏。相關源碼以下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private boolean gone(KeyedWeakReference reference) {
// 斷定被監控的activity對象對應的key
// 是否存在於retainedKeys集合中
// 若是不存在,說明該對象已經被GC回收,不存在內存泄漏
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
// 斷定被監控的activity引用是否保存在ReferenceQueue中
// 若是存在,則將其對應的key從retainedKeys集合中移除
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
複製代碼
2)將堆內存轉儲到文件並分析,獲取泄漏對象的GC最短強引用
路徑
前面說到,若是Gone方法返回false說明被監控的activity對象發生了內存泄漏,接下來,將進入內存泄漏分析過程。具體策略爲:首先,建立一個heapDump文件,此時尚未寫入;而後,將堆內存信息轉儲到該heapDump文件,並建立一個HeapDump對象;最後,調用heapdumpListener的analyze方法進入分析流程。
須要注意的是,heapdumpListener由RefWatch構造方法傳入,前面說到RefWatch對象是經過建造者模式的形式建立的,所以,咱們找到了AndroidRefWatcherBuilder,該類包含一個defaultHeapDumpListener方法便可說明heapdumpListener(類型爲HeapDump.Listener
)的實例化過程,即實現類爲ServiceHeapDumpListener。也就是說,heapdumpListener.analyze
爲調用ServiceHeapDumpListener#analyze方法,該方法中最終調用的是HeapAnalyzerService#runAnalysis方法在後臺服務中執行堆快照分析任務。相關源碼以下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ServiceHeapDumpListener.java
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(@NonNull final Context context, @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
this.listenerServiceClass = checkNotNull(listenerServiceClass,
"listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
// 後臺執行分析任務
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
複製代碼
接下來,咱們分析HeapAnalyzerService#runAnalysis方法,它的源碼以下:
public final class HeapAnalyzerService extends ForegroundService implements AnalyzerProgressListener {
...
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
// listenerServiceClass
// 負責記錄日誌和展現通知
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
// 啓動自身,並將其置爲前臺
ContextCompat.startForegroundService(context, intent);
}
}
12345678910111213141516
複製代碼
從HeapAnalyzerService源碼可知,它是一個IntentServcie
,實際上就是一個Service,只是與普通Service不一樣的是,它被啓動後會在子線程執行具體的任務,即調用onHandleIntentInForeground方法執行任務,當任務執行完畢後,該Service會被自動銷燬。在HeapAnalyzerService#runAnalysis
方法中,就是啓動該IntentService,並將其置爲前臺服務,以下降被系統殺死的機率。如今,咱們就看下HeapAnalyzerService#onHandleIntentInForeground
方法作了什麼,源碼以下:
protected void onHandleIntentInForeground(@Nullable Intent intent) {
// DisplayLeakService.class
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// 建立HeapAnalyzer
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this,
heapDump.reachabilityInspectorClasses);
// HeapAnanlyzer工具分析
// 即分析堆內存快照,找出 GC roots 的最短強引用路徑,並肯定是不是泄露
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile,
heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 啓動DisplayLeakService記錄日誌和展現通知
AbstractAnalysisResultService.sendResultToListener(this,
listenerClassName,
heapDump, result);
}
複製代碼
從該方法源碼可知,它主要是建立一個HeapAnalyzer對象,並調用該對象的checkForLeak進行分析,而後將獲得的結果交給DisplayLeakService進行通知展現。這裏,咱們分析下HeapAnalyzer#checkForLeak
方法分析堆內存快照文件的流程,該方法源碼以下:
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);
// 將heap文件封裝成MemoryMappedFileBuffer
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
// 建立hprof解析器,解析hprof文件
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 移除相同GC root
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) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//根據leakingRef尋找引用路徑
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef,
computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
複製代碼
由上述源碼可知,該方法最終調用findLeakingReference
方法來判斷是否真的存在內存泄漏,若是存在(leakingRef!=null
),就調用findLeakTrace
方法找出這個泄漏對象的GC Root最短強引用路徑。
ActivityLifecycleCallbacks#onActivityDestroyed
方法,而LeakCanary要作的就是在該方法中調用RefWatcher#watch
方法實現對activity進行內存泄漏監控。強引用
路徑,並肯定是不是泄露,若是泄漏,創建致使泄露的引用鏈。