一贪壳、leakCanary概念了解
1堵泽、leakCanary工作流程
LeakCannary 的主要原理筒愚,其實(shí)很簡單赴蝇,大概可以分為以下幾步:
- (1) 監(jiān)測Activity 的生命周期的 onDestroy() 的調(diào)用。
- (2) 當(dāng)某個(gè) Activity 的 onDestroy() 調(diào)用后巢掺,便對這個(gè) activity 創(chuàng)建一個(gè)帶 ReferenceQueue 的弱引用句伶,并且給這個(gè)弱引用創(chuàng)建了一個(gè) key 保存在 Set集合 中。
- (3) 如果這個(gè) activity 可以被回收陆淀,那么弱引用就會被添加到 ReferenceQueue 中考余。
- (4) 等待主線程進(jìn)入 idle(即空閑)后,通過一次遍歷轧苫,在 ReferenceQueue 中的弱引用所對應(yīng)的 key 將從 retainedKeys 中移除楚堤,說明其沒有內(nèi)存泄漏。
- (5) 如果 activity 沒有被回收含懊,先強(qiáng)制進(jìn)行一次 gc身冬,再來檢查,如果 key 還存在 retainedKeys 中岔乔,說明 activity 不可回收酥筝,同時(shí)也說明了出現(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中引用類型
- 強(qiáng)引用:不會被GC回收
- 軟引用:內(nèi)存不足的時(shí)候會被GC回收
- 弱引用:當(dāng)下次GC的時(shí)候會回收
- 虛引用:任何情況都可以回收
二、leakCarcry分析
在分析Leak Canary原理之前,我們先來簡單了解WeakReference和ReferenceQueue的作用萤悴,為什么要了解這些知識呢睡汹?Leak Canary其實(shí)內(nèi)部就是使用這個(gè)機(jī)制來監(jiān)控對象是否被回收了,當(dāng)然Leak Canary的監(jiān)控僅僅針對Activity和Fragment相嵌,所以這塊有引入了ActivityLifecycleCallBack腿时,后面會說,這里的回收是指JVM在合適的時(shí)間觸發(fā)GC饭宾,并將回收的WeakReference對象放入與之關(guān)聯(lián)的ReferenceQueue中表示GC回收了該對象批糟,Leak Canary通過上賣弄的檢測返現(xiàn)有些對象的生命周期本該已經(jīng)結(jié)束了,但是任然在占用內(nèi)存看铆,這時(shí)候就判定是已經(jīng)泄露了徽鼎,那么下一步就是開始解析析headump文件,分析引用鏈弹惦,至此就結(jié)束了否淤,其中需要注意的是這是WeakReference.get方法獲取到的對象是null,所以Leak Canary使用了繼承WeakReference.類棠隐,并把傳入的對象作為成員變量保存起來石抡,這樣當(dāng)GC發(fā)生時(shí)雖然把WeakReference中引用的對象置為了null也不會把WeakReference中我們拓展的類的成員變量置為null,這樣我們就可以做其他的操作助泽,比如:Leak Canary中把WeakReference存放在Set集合中啰扛,在恰當(dāng)?shù)臅r(shí)候需要移除Set中的WeakReference的引用,這個(gè)機(jī)制Glide中的內(nèi)存緩存 也是使用了該機(jī)制嗡贺,關(guān)于WeakReference和ReferenceQueue機(jī)制就不多說網(wǎng)上有很多可以了解一下隐解。
1、WeakReference和ReferenceQueue機(jī)制
/**
* 監(jiān)控對象被回收暑刃,因?yàn)槿绻换厥站蜁腿缗c之關(guān)聯(lián)的隊(duì)列中
*/
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
上面是一個(gè)監(jiān)控對象回收厢漩,因?yàn)槿绻麑ο蟊换厥站桶言搶ο蠹尤肴缗c之關(guān)聯(lián)的隊(duì)列中,接著開啟線程制造觸發(fā)GC岩臣,并開啟線程監(jiān)控對象回收溜嗜,Leak Canary也是利用這個(gè)機(jī)制完成對一些對象本該生命周期已經(jīng)結(jié)束,還常駐內(nèi)存架谎,就算觸發(fā)GC也不會回收炸宵,Leak Canary就判斷為泄漏,針對于內(nèi)存泄漏谷扣,我們知道有些對象是不能被GC回收的土全,JVM虛擬機(jī)的回收就是可達(dá)性算法,就是從GC Root開始檢測会涎,如果不可達(dá)那么就會被第一次標(biāo)志裹匙,再次GC就會被回收。
2末秃、能夠作為 GC Root的對象
- 虛擬機(jī)棧概页,在大家眼里也叫作棧(棧幀中的本地變量表)中引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象练慕;
- 方法區(qū)中常量引用的對象惰匙;
- 本地方法棧中JNI引用的對象技掏;
3、Leak Canary是如何判斷Activity或Fragment的生命周期結(jié)束了呢项鬼?
- Leak Canary是通過 Application的內(nèi)部類ActivityLifecycleCallbacks檢測Activity的生命周期是否結(jié)束了哑梳,如果回調(diào)了onActivityDestroyed方法,那么表示Activity的聲明周期已經(jīng)結(jié)束了绘盟,這時(shí)候就要執(zhí)行GC檢測了鸠真。
- 對于Fragment是通過FragmentManager的內(nèi)部接口FragmentLifecycleCallbacks檢測Fragment的聲明周期的類似ActivityLifecycleCallbacks接口。
4奥此、開始Leak Canary源碼解讀
步驟無非就是:
1弧哎、安裝雁比,實(shí)際上就是做一些初始化的操作稚虎;
2、檢測時(shí)機(jī)偎捎,比如:回調(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方法之前有個(gè)判斷丈牢,這個(gè)判斷是用來判斷是否是在LeakCanary的堆統(tǒng)計(jì)進(jìn)程(HeapAnalyzerService)祭钉,也就是我們不能在我們的App進(jìn)程中初始化LeakCanary,代碼如下:
/**
* 當(dāng)前進(jìn)程是否是運(yùn)行{@link HeapAnalyzerService}的進(jìn)程中己沛,這是一個(gè)與普通應(yīng)用程序進(jìn)程不同的進(jìn)程慌核。
*/
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// 這里只需要為每個(gè)進(jìn)程計(jì)算一次。
if (isInAnalyzerProcess == null) {
//把Context和HeapAnalyzerService服務(wù)作為參數(shù)傳進(jìn)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;
}
//主進(jìn)程
String mainProcess = packageInfo.applicationInfo.processName;
//構(gòu)造進(jìn)程
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;
}
//判斷當(dāng)前HeapAnalyzerService服務(wù)進(jìn)程名和主進(jìn)程名是否相等垮卓,如果相等直接返回false,因?yàn)長eakCanary不能再當(dāng)前進(jìn)程中運(yùn)行
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);
}
實(shí)際上LeakCanary最終會調(diào)用LeakCanaryInternals.isInServiceProcess方法师幕,通過PackageManager粟按、ActivityManager以及android.os.Process來判斷當(dāng)前進(jìn)程是否為HeapAnalyzerService運(yùn)行的進(jìn)程,因?yàn)槲覀儾荒茉谖覀兊腁pp進(jìn)程中初始化LeakCanary霹粥。
接下來我們開始LeakCanary真正的實(shí)現(xiàn)灭将,從LeakCanary.install(this)方法開始,代碼如下:
public static RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
實(shí)際上這一步返回的RefWatcher的實(shí)現(xiàn)類AndroidRefWatcher后控,主要是做些關(guān)乎初始化的操作庙曙,這些不展開講,直接進(jìn)入buildAndInstall()方法中忆蚀,代碼如下:
public RefWatcher buildAndInstall() {
//install()方法只能一次調(diào)用矾利,多次調(diào)用將拋出異常
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//初始化RefWatcher姑裂,這個(gè)東西是用來檢查內(nèi)存泄漏的,包括解析堆轉(zhuǎn)儲文件這些東西
RefWatcher refWatcher = build();
//如果RefWatcher還沒有初始化男旗,就會進(jìn)入這個(gè)分支
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
//setEnabledAsync最終調(diào)用了packageManager.setComponentEnabledSetting,
// 將Activity組件設(shè)置為可用舶斧,即在manifest中enable屬性。
// 也就是說察皇,當(dāng)我們運(yùn)行LeakCanary.install(this)的時(shí)候,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ǎn):
- 首先會調(diào)用RefWatcherBuilder.build方法創(chuàng)建RefWatcher琉雳,RefWatcher是檢測內(nèi)存泄漏相關(guān)的;
- 緊接著將Activity組件設(shè)置為可用友瘤,即在manifest中enable屬性翠肘,也就是說,當(dāng)我們運(yùn)行LeakCanary.install(this)的時(shí)候,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)控對象的引用是否可達(dá)笛求,當(dāng)引用變成不可達(dá)廊移,那么就會觸發(fā)堆轉(zhuǎn)儲(HeapDumper),來看看RefWatcherBuilder.build方法的具體實(shí)現(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)建堆轉(zhuǎn)儲對象
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
//返回的是HeapDumper.NONE,HeapDumper內(nèi)部實(shí)現(xiàn)類,
heapDumper = defaultHeapDumper();
}
//創(chuàng)建監(jiān)控線程池
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//默認(rèn)返回 NONE
watchExecutor = defaultWatchExecutor();
}
//默認(rèn)的Gc觸發(fā)器
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
//創(chuàng)建把參數(shù)構(gòu)造RefWatcher
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
如上代碼知道蜂嗽,實(shí)際上是為了創(chuàng)建RefWatcher實(shí)例苗膝,和一些在檢測中的環(huán)境初始化,比如線程池植旧、GC觸發(fā)器等等辱揭。
回到最初的biuldInstall方法中离唐,知道監(jiān)控Activity和Fragment是查不到的所以這里就只分析Activity相關(guān)的,也就是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)聽每一個(gè)Activity的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
可以知道這里是使用的裝飾模式亥鬓,使用ActivityRefWatcher對RefWatcher做了包裝,接著注冊ActivityLifecycleCallbacks監(jiān)聽每一個(gè)Activity的生命周期的onActivityDestroyed方法域庇,這也就是檢測泄漏開始的地方嵌戈,而在onActivityDestroyed方法方法中會調(diào)用refWatcher.watch方法把a(bǔ)ctivity作為參數(shù)傳進(jìn)去,代碼如下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
//當(dāng)Activity被銷毀了听皿,那么應(yīng)該檢測是否內(nèi)存泄漏
refWatcher.watch(activity);
}
};
可以看到在Activity銷毀時(shí)會回調(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中尉姨,強(qiáng)引用
retainedKeys.add(key);
//KeyedWeakReference 繼承至WeakReference庵朝,由于KeyedWeakReference如果回收了,那么當(dāng)中的對象通過get返回的是null啊送,
// 所以需要保存key和name作為標(biāo)識偿短,Glide也是此做法,KeyedWeakReference實(shí)現(xiàn)WeakReference
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開始檢測
ensureGoneAsync(watchStartNanoTime, reference);
}
在watch方法中有如下幾點(diǎn):
- 首先通過UUID生成表示該引用的Key馋没,而這個(gè)Key會當(dāng)做強(qiáng)引用保存到RefWatcher的成員變量Set集合中;
- 接著創(chuàng)建KeyedWeakReference降传,而KeyedWeakReference 繼承至WeakReference篷朵,由于KeyedWeakReference如果回收了,那么當(dāng)中的對象通過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),而這個(gè)線程池就是在剛開始的時(shí)候LeakCanary.install方法中創(chuàng)建RefWatcher的子類AndroidRefWatcher的時(shí)候創(chuàng)建的赞枕,接著看看ensureGone方法澈缺,代碼如下:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//gc 開始的時(shí)間
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊(duì)列了炕婶,如果已經(jīng)加入隊(duì)列說明不會造成泄漏的風(fēng)險(xiǎn)
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
//嘗試GC
gcTrigger.runGc();
//從Set中移除不能訪問引用姐赡,意思就是GC之后該引用對象是否加入隊(duì)列了,如果已經(jīng)加入隊(duì)列說明不會造成泄漏的風(fēng)險(xiǎn)
removeWeaklyReachableReferences();
//到這一步說明該對象按理來說聲明周期是已經(jīng)結(jié)束了的柠掂,但是通過前面的GC卻不能回收项滑,說明已經(jīng)造成了內(nèi)存泄漏,那么解析hprof文件涯贞,得到該對象的引用鏈枪狂,也就是要觸發(fā)堆轉(zhuǎn)儲
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();
//開始解釋堆轉(zhuǎn)儲文件
heapdumpListener.analyze(heapDump);
}
return DONE;
}
在ensureGone方法中有如下幾點(diǎn):
- 調(diào)用removeWeaklyReachableReferences方法危喉,從Set中移除不能訪問引用,意思就是GC之后該引用對象是否加入隊(duì)列了州疾,如果已經(jīng)加入隊(duì)列說明不會造成泄漏的風(fēng)險(xiǎn)姥饰,就將引用從Set集合中移除;
- 緊接著調(diào)用gcTrigger.runGc方法嘗試GC孝治,看看能不能回收引用對象列粪;
- 再次調(diào)用removeWeaklyReachableReferences方法,從Set中移除不能訪問引用谈飒,意思就是GC之后該引用對象是否加入隊(duì)列了岂座,如果已經(jīng)加入隊(duì)列說明不會造成泄漏的風(fēng)險(xiǎn),也就是在手動觸發(fā)GC之后杭措,再次檢測是否可以回收對象费什;
- *最后通過gone(reference)方法檢測Set集合中是否還存在該對象,如果存在說明已經(jīng)泄漏了手素,就像前面說的鸳址,如果發(fā)生GC并且對象是可以被回收的,那么就會加入引用隊(duì)列泉懦, 最后到這一步說明該對象按理來說聲明周期是已經(jīng)結(jié)束了的稿黍,但是通過前面的GC卻不能回收,說明已經(jīng)造成了內(nèi)存泄漏崩哩,那么解析hprof文件巡球,得到該對象的引用鏈,也就是要觸發(fā)堆轉(zhuǎn)儲邓嘹。
在前面說很多檢測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會在它們指向的對象變得無法訪問時(shí)排隊(duì)汹押。 這是在完成或垃圾收集實(shí)際發(fā)生之前矿筝。
//隊(duì)列不為null,說明該對象被收了棚贾,加入此隊(duì)列
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
這里直接使用一個(gè)while循環(huán)從隊(duì)列取出元素進(jìn)行判斷窖维,這里的queue.poll()是不會阻塞的,所以為什么LeakCanary會做兩次驗(yàn)證的原因鸟悴,為什么LeakCanary不使用queue.remove()方法呢陈辱?你想想queue.remove()方法是阻塞當(dāng)前線程,從前面知道每次Activity或者Fragment銷毀回調(diào)生命周期方法都會創(chuàng)建一個(gè)KeyedWeakReference實(shí)例细诸,也就是說如果不泄露就一直阻塞當(dāng)前線程沛贪,這樣反而對造成不必要的開銷,我也是猜猜而已。
6利赋、總結(jié)
- LeakCanary是通過WeakReference+Reference機(jī)制檢測對象是否能被回收水评;
- LeakCanary檢測的時(shí)機(jī)是當(dāng)某組件的生命周期已經(jīng)結(jié)束,才會觸發(fā)檢測媚送;
參考鏈接:http://www.reibang.com/p/fa9d4eae7f05
所以說LeakCanary針對Activity/Fragment的內(nèi)存泄漏檢測非常好用中燥,但是對于以上檢測不到的情況,還得配合Android Monitor + MAT