一、leakCanary概念了解
1、leakCanary工作流程
LeakCannary 的主要原理,其實很簡單,大概可以分為以下幾步:
- (1) 監(jiān)測Activity 的生命周期的 onDestroy() 的調(diào)用。
- (2) 當某個 Activity 的 onDestroy() 調(diào)用后,便對這個 activity 創(chuàng)建一個帶 ReferenceQueue 的弱引用,并且給這個弱引用創(chuàng)建了一個 key 保存在 Set集合 中。
- (3) 如果這個 activity 可以被回收,那么弱引用就會被添加到 ReferenceQueue 中。
- (4) 等待主線程進入 idle(即空閑)后,通過一次遍歷,在 ReferenceQueue 中的弱引用所對應的 key 將從 retainedKeys 中移除,說明其沒有內(nèi)存泄漏。
- (5) 如果 activity 沒有被回收,先強制進行一次 gc,再來檢查,如果 key 還存在 retainedKeys 中,說明 activity 不可回收,同時也說明了出現(xiàn)了內(nèi)存泄漏。
- (6) 發(fā)生內(nèi)存泄露之后,dump內(nèi)存快照,分析 hprof 文件,找到泄露路徑(使用 haha 庫分析),發(fā)送到通知欄
LeakCanary對于內(nèi)存泄漏的檢測非常有效,但也并不是所有的內(nèi)存泄漏都能檢測出來。
- 1、無法檢測出Service中的內(nèi)存泄漏問題
- 2、如果最底層的MainActivity一直未走onDestroy生命周期(它在Activity棧的最底層),無法檢測出它的調(diào)用棧的內(nèi)存泄漏。

2、java中的4中引用類型
- 強引用:不會被GC回收
- 軟引用:內(nèi)存不足的時候會被GC回收
- 弱引用:當下次GC的時候會回收
- 虛引用:任何情況都可以回收
二、leakCarcry分析
在分析Leak Canary原理之前,我們先來簡單了解WeakReference和ReferenceQueue的作用,為什么要了解這些知識呢?Leak Canary其實內(nèi)部就是使用這個機制來監(jiān)控對象是否被回收了,當然Leak Canary的監(jiān)控僅僅針對Activity和Fragment,所以這塊有引入了ActivityLifecycleCallBack,后面會說,這里的回收是指JVM在合適的時間觸發(fā)GC,并將回收的WeakReference對象放入與之關聯(lián)的ReferenceQueue中表示GC回收了該對象,Leak Canary通過上賣弄的檢測返現(xiàn)有些對象的生命周期本該已經(jīng)結束了,但是任然在占用內(nèi)存,這時候就判定是已經(jīng)泄露了,那么下一步就是開始解析析headump文件,分析引用鏈,至此就結束了,其中需要注意的是這是WeakReference.get方法獲取到的對象是null,所以Leak Canary使用了繼承WeakReference.類,并把傳入的對象作為成員變量保存起來,這樣當GC發(fā)生時雖然把WeakReference中引用的對象置為了null也不會把WeakReference中我們拓展的類的成員變量置為null,這樣我們就可以做其他的操作,比如:Leak Canary中把WeakReference存放在Set集合中,在恰當?shù)臅r候需要移除Set中的WeakReference的引用,這個機制Glide中的內(nèi)存緩存 也是使用了該機制,關于WeakReference和ReferenceQueue機制就不多說網(wǎng)上有很多可以了解一下。
1、WeakReference和ReferenceQueue機制
/**
* 監(jiān)控對象被回收,因為如果被回收就會就如與之關聯(lián)的隊列中
*/
private void monitorClearedResources() {
Log.e("tag", "start monitor");
try {
int n = 0;
WeakReference k;
while ((k = (WeakReference) referenceQueue.remove()) != null) {
Log.e("tag", (++n) + "回收了:" + k + " object: " + k.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ReferenceQueue<WeakRefrencesBean> referenceQueue = new ReferenceQueue<>();
class WeakRefrencesBean {
private String name;
public WeakRefrencesBean(String name) {
this.name = name;
}
}
new Thread() {
@Override
public void run() {
monitorClearedResources();
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
new WeakReference<WeakRefrencesBean>(new WeakRefrencesBean("aaaa"), referenceQueue);
}
}
}.start();
輸出的日志:
1回收了:java.lang.ref.WeakReference@21f8376e object: null
2回收了:java.lang.ref.WeakReference@24a74e0f object: null
3回收了:java.lang.ref.WeakReference@39efe9c object: null
4回收了:java.lang.ref.WeakReference@4ee20a5 object: null
3回收了:java.lang.ref.WeakReference@bf45c7a object: null
4回收了:java.lang.ref.WeakReference@b94bc2b object: null
5回收了:java.lang.ref.WeakReference@33eb6888 object: null
上面是一個監(jiān)控對象回收,因為如果對象被回收就把該對象加入如與之關聯(lián)的隊列中,接著開啟線程制造觸發(fā)GC,并開啟線程監(jiān)控對象回收,Leak Canary也是利用這個機制完成對一些對象本該生命周期已經(jīng)結束,還常駐內(nèi)存,就算觸發(fā)GC也不會回收,Leak Canary就判斷為泄漏,針對于內(nèi)存泄漏,我們知道有些對象是不能被GC回收的,JVM虛擬機的回收就是可達性算法,就是從GC Root開始檢測,如果不可達那么就會被第一次標志,再次GC就會被回收。
2、能夠作為 GC Root的對象
- 虛擬機棧,在大家眼里也叫作棧(棧幀中的本地變量表)中引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象;
- 方法區(qū)中常量引用的對象;
- 本地方法棧中JNI引用的對象;
3、Leak Canary是如何判斷Activity或Fragment的生命周期結束了呢?
- Leak Canary是通過 Application的內(nèi)部類ActivityLifecycleCallbacks檢測Activity的生命周期是否結束了,如果回調(diào)了onActivityDestroyed方法,那么表示Activity的聲明周期已經(jīng)結束了,這時候就要執(zhí)行GC檢測了。
- 對于Fragment是通過FragmentManager的內(nèi)部接口FragmentLifecycleCallbacks檢測Fragment的聲明周期的類似ActivityLifecycleCallbacks接口。
4、開始Leak Canary源碼解讀
步驟無非就是:
1、安裝,實際上就是做一些初始化的操作;
2、檢測時機,比如:回調(diào)onActivityDestroyed方法開始檢測;
3、UI的展示;
5、安裝
Leak Canary的地方就是 LeakCanary.install(this)方法開始,代碼如下:
一般我們使用Leak Canaryu都是在Application中調(diào)用:
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
setupLeakCanary();
}
protected void setupLeakCanary() {
enabledStrictMode();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
...
}
在install方法之前有個判斷,這個判斷是用來判斷是否是在LeakCanary的堆統(tǒng)計進程(HeapAnalyzerService),也就是我們不能在我們的App進程中初始化LeakCanary,代碼如下:
/**
* 當前進程是否是運行{@link HeapAnalyzerService}的進程中,這是一個與普通應用程序進程不同的進程。
*/
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// 這里只需要為每個進程計算一次。
if (isInAnalyzerProcess == null) {
//把Context和HeapAnalyzerService服務作為參數(shù)傳進isInServiceProcess方法中
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
在isInAnalyzerProcess方法中有調(diào)用了isInServiceProcess方法,代碼如下:
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
//主進程
String mainProcess = packageInfo.applicationInfo.processName;
//構造進程
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, PackageManager.GET_DISABLED_COMPONENTS);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
//判斷當前HeapAnalyzerService服務進程名和主進程名是否相等,如果相等直接返回false,因為LeakCanary不能再當前進程中運行
if (serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses;
try {
runningProcesses = activityManager.getRunningAppProcesses();
} catch (SecurityException exception) {
// https://github.com/square/leakcanary/issues/948
CanaryLog.d("Could not get running app processes %d", exception);
return false;
}
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
實際上LeakCanary最終會調(diào)用LeakCanaryInternals.isInServiceProcess方法,通過PackageManager、ActivityManager以及android.os.Process來判斷當前進程是否為HeapAnalyzerService運行的進程,因為我們不能在我們的App進程中初始化LeakCanary。
接下來我們開始LeakCanary真正的實現(xiàn),從LeakCanary.install(this)方法開始,代碼如下:
public static RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
實際上這一步返回的RefWatcher的實現(xiàn)類AndroidRefWatcher,主要是做些關乎初始化的操作,這些不展開講,直接進入buildAndInstall()方法中,代碼如下:
public RefWatcher buildAndInstall() {
//install()方法只能一次調(diào)用,多次調(diào)用將拋出異常
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//初始化RefWatcher,這個東西是用來檢查內(nèi)存泄漏的,包括解析堆轉儲文件這些東西
RefWatcher refWatcher = build();
//如果RefWatcher還沒有初始化,就會進入這個分支
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
//setEnabledAsync最終調(diào)用了packageManager.setComponentEnabledSetting,
// 將Activity組件設置為可用,即在manifest中enable屬性。
// 也就是說,當我們運行LeakCanary.install(this)的時候,LeakCanary的icon才顯示出來
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//ActivityRefWatcher.install和FragmentRefWatcher.Helper.install的功能差不多,注冊了生命周期監(jiān)聽。
// 不同的是,前者用application監(jiān)聽Activity的生命周期,后者用Activity監(jiān)聽也就是Activity回調(diào)onActivityCreated方法,
// 然后獲取FragmentManager調(diào)用registerFragmentLifecycleCallbacks方法注冊,監(jiān)聽fragment的生命周期,
// 而且用到了leakcanary-support-fragment包,兼容了v4的fragment。
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
在buildAndInstall方法中有幾點:
- 首先會調(diào)用RefWatcherBuilder.build方法創(chuàng)建RefWatcher,RefWatcher是檢測內(nèi)存泄漏相關的;
- 緊接著將Activity組件設置為可用,即在manifest中enable屬性,也就是說,當我們運行LeakCanary.install(this)的時候,LeakCanary的icon才在桌面才會顯示出來;
- 然后就是ActivityRefWatcher.install和FragmentRefWatcher.Helper.install方法,注冊了Activity和Fragment的生命周期監(jiān)聽,不同的是,前者用application監(jiān)聽Activity的生命周期,后者用Activity監(jiān)聽也就是Activity回調(diào)onActivityCreated方法,然后通過Activity獲取FragmentManager調(diào)用并FragmentManager的registerFragmentLifecycleCallbacks方法注冊監(jiān)聽fragment的生命周期,而且用到了leakcanary-support-fragment包,兼容了v4的fragment。
RefWatcher類是用來監(jiān)控對象的引用是否可達,當引用變成不可達,那么就會觸發(fā)堆轉儲(HeapDumper),來看看RefWatcherBuilder.build方法的具體實現(xiàn),代碼如下:
public final RefWatcher build() {
//如果已經(jīng)初始化了,直接返回RefWatcher.DISABLED表示已經(jīng)初始化了
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
//創(chuàng)建堆轉儲對象
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
//返回的是HeapDumper.NONE,HeapDumper內(nèi)部實現(xiàn)類,
heapDumper = defaultHeapDumper();
}
//創(chuàng)建監(jiān)控線程池
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//默認返回 NONE
watchExecutor = defaultWatchExecutor();
}
//默認的Gc觸發(fā)器
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
//創(chuàng)建把參數(shù)構造RefWatcher
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
如上代碼知道,實際上是為了創(chuàng)建RefWatcher實例,和一些在檢測中的環(huán)境初始化,比如線程池、GC觸發(fā)器等等。
回到最初的biuldInstall方法中,知道監(jiān)控Activity和Fragment是查不到的所以這里就只分析Activity相關的,也就是ActivityRefWatcher.install方法,代碼如下:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//注冊ActivityLifecycleCallbacks監(jiān)聽每一個Activity的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
可以知道這里是使用的裝飾模式,使用ActivityRefWatcher對RefWatcher做了包裝,接著注冊ActivityLifecycleCallbacks監(jiān)聽每一個Activity的生命周期的onActivityDestroyed方法,這也就是檢測泄漏開始的地方,而在onActivityDestroyed方法方法中會調(diào)用refWatcher.watch方法把activity作為參數(shù)傳進去,代碼如下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
//當Activity被銷毀了,那么應該檢測是否內(nèi)存泄漏
refWatcher.watch(activity);
}
};
可以看到在Activity銷毀時會回調(diào)onActivityDestroyed方法,然后把該activity作為參數(shù)傳遞給refWatcher.watch(activity)方法,watch方法代碼如下:
public void watch(Object watchedReference, String referenceName) {
..........
final long watchStartNanoTime = System.nanoTime();
//給該引用生成UUID
String key = UUID.randomUUID().toString();
//給該引用的UUID保存至Set中,強引用
retainedKeys.add(key);
//KeyedWeakReference 繼承至WeakReference,由于KeyedWeakReference如果回收了,那么當中的對象通過get返回的是null,
// 所以需要保存key和name作為標識,Glide也是此做法,KeyedWeakReference實現(xiàn)WeakReference
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開始檢測
ensureGoneAsync(watchStartNanoTime, reference);
}
在watch方法中有如下幾點:
- 首先通過UUID生成表示該引用的Key,而這個Key會當做強引用保存到RefWatcher的成員變量Set集合中;
- 接著創(chuàng)建KeyedWeakReference,而KeyedWeakReference 繼承至WeakReference,由于KeyedWeakReference如果回收了,那么當中的對象通過get返回的是null,所以為了能在GC之后拿到Key,需要將保存key和name作為KeyedWeakReference中,Glide也是此做法;
- 接著調(diào)用ensureGoneAsync(watchStartNanoTime, reference)方法開始檢測是否有內(nèi)存泄漏;
ensureGoneAsync方法代碼如下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
在ensureGoneAsync方法中直接執(zhí)行線程池(AndroidWatchExecutor),而這個線程池就是在剛開始的時候LeakCanary.install方法中創(chuàng)建RefWatcher的子類AndroidRefWatcher的時候創(chuàng)建的,接著看看ensureGone方法,代碼如下:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//gc 開始的時間
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊列了,如果已經(jīng)加入隊列說明不會造成泄漏的風險
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
//嘗試GC
gcTrigger.runGc();
//從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊列了,如果已經(jīng)加入隊列說明不會造成泄漏的風險
removeWeaklyReachableReferences();
//到這一步說明該對象按理來說聲明周期是已經(jīng)結束了的,但是通過前面的GC卻不能回收,說明已經(jīng)造成了內(nèi)存泄漏,那么解析hprof文件,得到該對象的引用鏈,也就是要觸發(fā)堆轉儲
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;
}
在ensureGone方法中有如下幾點:
- 調(diào)用removeWeaklyReachableReferences方法,從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊列了,如果已經(jīng)加入隊列說明不會造成泄漏的風險,就將引用從Set集合中移除;
- 緊接著調(diào)用gcTrigger.runGc方法嘗試GC,看看能不能回收引用對象;
- 再次調(diào)用removeWeaklyReachableReferences方法,從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊列了,如果已經(jīng)加入隊列說明不會造成泄漏的風險,也就是在手動觸發(fā)GC之后,再次檢測是否可以回收對象;
- *最后通過gone(reference)方法檢測Set集合中是否還存在該對象,如果存在說明已經(jīng)泄漏了,就像前面說的,如果發(fā)生GC并且對象是可以被回收的,那么就會加入引用隊列, 最后到這一步說明該對象按理來說聲明周期是已經(jīng)結束了的,但是通過前面的GC卻不能回收,說明已經(jīng)造成了內(nèi)存泄漏,那么解析hprof文件,得到該對象的引用鏈,也就是要觸發(fā)堆轉儲。
在前面說很多檢測GC回收是怎么做到的呢,接下來看看removeWeaklyReachableReferences方法,代碼如下:
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.
//WeakReferences會在它們指向的對象變得無法訪問時排隊。 這是在完成或垃圾收集實際發(fā)生之前。
//隊列不為null,說明該對象被收了,加入此隊列
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
這里直接使用一個while循環(huán)從隊列取出元素進行判斷,這里的queue.poll()是不會阻塞的,所以為什么LeakCanary會做兩次驗證的原因,為什么LeakCanary不使用queue.remove()方法呢?你想想queue.remove()方法是阻塞當前線程,從前面知道每次Activity或者Fragment銷毀回調(diào)生命周期方法都會創(chuàng)建一個KeyedWeakReference實例,也就是說如果不泄露就一直阻塞當前線程,這樣反而對造成不必要的開銷,我也是猜猜而已。
6、總結
- LeakCanary是通過WeakReference+Reference機制檢測對象是否能被回收;
- LeakCanary檢測的時機是當某組件的生命周期已經(jīng)結束,才會觸發(fā)檢測;
參考鏈接:http://m.itdecent.cn/p/fa9d4eae7f05
所以說LeakCanary針對Activity/Fragment的內(nèi)存泄漏檢測非常好用,但是對于以上檢測不到的情況,還得配合Android Monitor + MAT