在Android開發中最讓人們頭疼的就是記憶體洩漏了,今天來介紹一個檢視記憶體是否洩漏的工具LeakCanary,并通過研究源碼明白它是如何分析和查找存在洩漏資訊的
在Android開發中最讓人們頭疼的就是記憶體洩漏了,今天來介紹一個檢視記憶體是否洩漏的工具LeakCanary,并通過研究源碼明白它是如何分析和查找存在洩漏資訊的
首先送上LeakCanary文檔連結:LeakCanary中文使用說明
Part1. 知識回顧常用工具Mat
LeakCanary(Square)
原理:watch監視一個即将要銷毀的對象記憶體種類1、棧(stack-基本資料類型,對象的引用)
2、堆(heap-存放new出來的對象和數組,在堆中配置設定記憶體由GC管理)
3、方法區(method,大體和堆一樣)為什麼會産生記憶體洩漏當一個對象已經不需要再使用了,在該對象被回收時候,有另外的對象引用該回收對象,導緻本該被回收的對象無法回收
有些對象隻有有限的生命周期,當生命周期很短的完成任務後,在本該結束的生命周期中仍然被引用記憶體洩漏會導緻什麼問題OOM常見的記憶體洩漏情況單例造成的記憶體洩漏
非靜态内部類建立靜态執行個體造成的記憶體洩漏
handler造成記憶體洩漏(handler、message、MessageQueue)
解決方法:①将Handler聲明為靜态類型
②通過弱引用的方式引入Activity
線程造成的記憶體洩漏(解決方法:将線程定義聲明為static類型)
webview造成的記憶體洩漏(example:加載頁面很複雜,Ex:大量的圖檔)
Part2 概念引用類型強引用(StrongReference),預設對象一般為強引用
軟引用(SoftReference),當記憶體空間足夠大時相當于強引用,記憶體不夠時通過垃圾回收器(GC)自動回收
弱引用(WeakReference),當GC掃描到該類型的引用時就自己回收
虛引用,相當于沒有進行引用,GC可随時回收該類型的引用ReferenceQueue軟引用和弱引用都持有該對象
對象被垃圾回收,Java虛拟機就會把這個引用加入到與之相關聯的引用隊列中
Part3.LeakCanary使用在module層級中的build.gradle中加入引用,不同的編譯使用不同的引用dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'}
2.在Application中:public class MyApplication extends Application { @Override
public void onCreate() { super.onCreate();
LeakCanary.install(this);
}
}
3.在Manifest.xml中加載該Application檔案
Part4. LeakCanary源碼剖析
從代碼入口剖析:LeakCanary.install(this);
跟蹤源碼可知
public static RefWatcher install(Application application) { return install(application, DisplayLeakService.class);
}
從上面的代碼我們發現這個方法最終傳回給我們一個RefWatcher這個類,這個類是主要是啟動ActivityRefWatcher類,ActivityRefWatcher在Activity的onDestory方法結束時檢測記憶體洩漏。
看下install這個方法:
public static RefWatcher install(Application application, Class extends AbstractAnalysisResultService> listenerServiceClass) { if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED;
}
enableDisplayLeakActivity(application);
HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass);
RefWatcher refWatcher = androidWatcher(application, heapDumpListener);
ActivityRefWatcher.installOnIcsPlus(application, refWatcher); return refWatcher;
}
通過RefWatcher refWatcher = androidWatcher(application, heapDumpListener)建立一個RefWatcher對象,啟動activityRefWatcher來監視記憶體洩漏
enableDisplayLeakActivity(application)主要作用是開啟DisplayLeakActivity這個類,這個類主要是顯示記憶體洩漏的彈框頁面ActivityRefWatcher.installOnIcsPlus(application, refWatcher);public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { if (SDK_INT
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
} public void watchActivities() { // Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
} public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
小結:
①通過stopWatcher方法反注冊以前的Activity的生命周期的callback,目的是為了保證以前的記憶體洩漏的activity删除
②重新注冊activity生命周期的callback
③通過lifecycleCallbacks中的onActivityDestroyed方法将activity的生命周期和ActivityReference關聯起來@Override
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
跟蹤onActivityDestroyed方法發現通過調用RefWatcher調用了watcher類void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}private final RefWatcher refWatcher
下面我們進入RefWatcher類中發現有如下的變量資訊private final Executor watchExecutor; private final DebuggerControl debuggerControl; private final GcTrigger gcTrigger; private final HeapDumper heapDumper; private final Set retainedKeys; private final ReferenceQueue queue; private final HeapDump.Listener heapdumpListener;
上述變量大意如下:watchExecutor主要用于執行記憶體洩漏檢測
debuggerControl查詢我們是否正在調試中,如果我們正在調試過程中則不會進行判斷
gcTrigger用于處理GC,用于在判斷洩漏對象之前再調用GC類中的方法再次判斷
heapDumper用于dump中記憶體洩漏堆檔案
retainedKeys該set集合持有待檢測和已産生記憶體洩漏資訊的key
queue引用對象,主要是判斷弱引用所持有的對象是否已執行GC垃圾收回
heapdumpListener主要用于分析産生hprof檔案回調
檢視watch方法可知:
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName"); if (debuggerControl.isDebuggerAttached()) { return;
} final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key); final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() { @Override public void run() {
ensureGone(reference, watchStartNanoTime);
}
});
}
通過産生一個唯一的key添加到retainedKeys集合中String key = UUID.randomUUID().toString();
retainedKeys.add(key);
再建立一個KeyedWeakReference的弱引用,并開啟一個異步線程來分析建立好的弱引用,該線程主要作用是確定我們的Activity是否真正已經進入到GONE狀态void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime();
//計算過去的方法到調用GC垃圾收回的時間值
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //清除已經到達我們引用隊列的弱引用
removeWeaklyReachableReferences(); //判斷如果處于debug狀态就不再進行記憶體分析
if (gone(reference) || debuggerControl.isDebuggerAttached()) { return;
}
gcTrigger.runGc();//手動進行垃圾回收
removeWeaklyReachableReferences(); if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();//dump出記憶體洩漏的檔案
if (heapDumpFile == null) { // Could not dump the heap, abort.
return;
} long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); //開始分析記憶體洩漏檔案查找記憶體洩漏路徑
heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
heapDumpDurationMs));
}
}
以上代碼部分總結如下:首先建立一個RefWatcher,啟動一個ActivityRefWatcher
通過ActivityLifecyclecallback将Activity的onDestroy生命周期給關聯起來
最後通過執行execute線程來分析洩漏資訊探讨LeakCanary中Activity洩漏檢測機制代碼
在上面的ensureGone方法中最後我們發現有這樣的代碼heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
heapDumpDurationMs));
}
通過跟蹤發現analyze方法該方法是HeapDump類中的一個interface接口,再檢視它的實作類發現在ServiceHeapDumpListener這個類中的方法@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
繼續跟蹤runAnalysis方法發現在HeapAnalyzerService中,且該類繼承了intentService,是以它将會每次調用onHandleIntent方法@Override
protected void onHandleIntent(Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
通過checkForLeak方法來分析記憶體洩漏資訊的結果,并通過sendResultToListener顯示最終的結果public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { long analysisStartNanoTime = System.nanoTime(); if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile); return failure(exception, since(analysisStartNanoTime));
}
ISnapshot snapshot = null; try {
snapshot = openSnapshot(heapDumpFile);//生成記憶體快照資訊
IObject leakingRef = findLeakingReference(referenceKey, snapshot);//檢視記憶體的引用
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) { return noLeak(since(analysisStartNanoTime));
}
String className = leakingRef.getClazz().getName();
AnalysisResult result =
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true); if (!result.leakFound) {
result = findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, false);//尋找記憶體洩漏的路徑
} return result;
} catch (SnapshotException e) { return failure(e, since(analysisStartNanoTime));
} finally {
cleanup(heapDumpFile, snapshot);
}
總結checkForLeak方法
1.把.hprof轉為 Snapshotsnapshot = openSnapshot(heapDumpFile);
2.找出洩漏的對象/洩漏對象的最短路徑IObject leakingRef = findLeakingReference(referenceKey, snapshot);
AnalysisResult result =
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true);
findLeakingReference作用①在snapshot快照中找到第一個弱引用即為記憶體發生洩漏的引用
②周遊這個對象的所有執行個體資訊
③如果發現存在key值與之前定義封裝好的key值相同,那麼傳回這個定位到的洩漏對象
findLeakTrace是通過擷取記憶體洩漏的引用來擷取洩漏路徑的最短路徑了解LeakCanary的原理Activity Destroy()之後将它放在一個WeakReference中
将WeakReference關聯到一個ReferenceQueue
檢視ReferenceQueue是否存有Activity的引用
如果該Activity洩露了,Dump出heap資訊,然後去分析洩漏路徑