LeakCanary原理淺析
1.LeakCanary簡(jiǎn)介
LeakCanary是一個(gè)Android和Java的內(nèi)存泄漏檢測(cè)庫(kù)变逃,可以大幅可以大幅度減少了開發(fā)中遇到的OOM問(wèn)題。
LeakCanary開源庫(kù)的地址為:
LeakCanary的README地址如下:
2.如何使用LeakCanary
使用LeakCanary非常簡(jiǎn)單嚷往,只需要在Application的onCreate()方法里面調(diào)用LeakCanary.install(this)方法陆馁,就像下面一樣:
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 如果是在HeapAnalyzer進(jìn)程里绳匀,則返回台腥,因?yàn)樵撨M(jìn)程是專門用來(lái)堆內(nèi)存分析的投放。
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
//調(diào)用LeakCanary.install()的方法來(lái)進(jìn)行必要的初始化工作奈泪,來(lái)監(jiān)聽內(nèi)存泄漏。
LeakCanary.install(this);
// Normal app init code...
}
}
如果只想檢測(cè)Activity的內(nèi)存泄漏,而且只想使用默認(rèn)的報(bào)告方式涝桅,則只需按照上面的實(shí)例拜姿,在Application里面添加一行代碼:
LeakCanary.install(this);
如果想監(jiān)控Fragment等其他有生命周期函數(shù)的方法的類是否有內(nèi)存泄漏時(shí),可以先返回一個(gè)RefWatcher對(duì)象冯遂,然后通過(guò)RefWatcher監(jiān)控那些本該被回收的對(duì)象蕊肥。使用示例如下:
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@Override public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
}
使用RefWatcher監(jiān)控Fragment:
public abstract class BaseFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
可以看到,不管哪種方式债蜜,都需要調(diào)用LeakCanary.install()方法晴埂,因此接下來(lái)的源碼分析將從這個(gè)方法開始。
3.LeakCanary源碼分析
第一部分:初始化工作
先來(lái)看下LeakCanary初始化工作時(shí)序圖:
接下來(lái)將從LeakCanary的install()方法開始分析LeakCanary的工作流程寻定。
3.1 LeakCanary.install()
/*
* 創(chuàng)建一個(gè)RefWatcher儒洛,用來(lái)監(jiān)控Activity的引用
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();//見3.2,3.3,3.4
}
refWatcher(application)方法返回的是一個(gè)AndroidRefWatcherBuilder,通過(guò)這個(gè)Builder來(lái)配置RefWatcher的一些設(shè)置參數(shù)狼速。
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
3.2 AndroidRefWatcherBuilder.listenerServiceClass()
/*
* 設(shè)置一個(gè)自定義的AbstractAnalysisResultService服務(wù)來(lái)監(jiān)聽內(nèi)存分析結(jié)果
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
通過(guò)ServiceHeapDumpListener類來(lái)保存listenerServiceClass服務(wù)琅锻,然后再通過(guò)RefWatcherBuilder來(lái)保存HeapDump的listener。
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabled(context, listenerServiceClass, true);
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");//保存listenerServiceClass
this.context = checkNotNull(context, "context").getApplicationContext();
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;//保存HeapDump的listener
return self();
}
可以看到向胡,listenerServiceClass()方法主要保存監(jiān)聽內(nèi)存分析結(jié)果的listener恼蓬,當(dāng)內(nèi)存分析結(jié)果完成后,則回調(diào)該listener僵芹,在這里這個(gè)listener是DisplayLeakService服務(wù)处硬。
3.3 RefWatcherBuilder.excludedRefs()
/*
* 該方法主要是排除一些開發(fā)可以忽略的泄漏路徑(一般是系統(tǒng)級(jí)別BUG),這些枚舉在AndroidExcludedRefs這個(gè)類當(dāng)中定義
*/
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
由于AndroidRefWatcherBuilder繼承自RefWatcherBuilder拇派,excludedRefs()方法只在RefWatcherBuilder類中定義荷辕,因此調(diào)用的是RefWatcherBuilder.excludedRefs()方法,該方法的主要作用是告訴需要忽略哪些已知的系統(tǒng)泄漏Bug件豌,避免這些結(jié)果產(chǎn)生干擾疮方。
3.4 AndroidRefWatcherBuilder.buildAndInstall()
/*
* 創(chuàng)建一個(gè)RefWatcher實(shí)例,并且開始監(jiān)控Activity實(shí)例引用
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();//構(gòu)建一個(gè)RefWatcher茧彤,見3.5
// 如果RefWatcher可用骡显,則讓泄漏通知顯示服務(wù)生效,并且注冊(cè)監(jiān)聽曾掂。
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);//注冊(cè)監(jiān)聽惫谤,見3.6
}
return refWatcher;
}
該方法的主要作用是構(gòu)建一個(gè)RefWatcher類,并判斷RefWatcher是否可用遭殉,如果可用的話石挂,則讓泄漏通知顯示服務(wù)生效,并且注冊(cè)監(jiān)聽险污。
3.5 RefWatcherBuilder.build()
/*
* 構(gòu)建一個(gè)RefWatcher對(duì)象
*/
public final RefWatcher build() {
//如果禁用了痹愚,則返回
if (isDisabled()) {
return RefWatcher.DISABLED;
}
// 保留被排出系統(tǒng)的泄漏Bug
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
// 保存HeapDump的listener富岳,監(jiān)聽內(nèi)存分析結(jié)果
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
// 保存調(diào)試控制,如果是處理調(diào)試階段拯腮,則忽略內(nèi)存泄漏監(jiān)控
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
// 保存HeapDumper窖式,如果沒(méi)有定義,則使用默認(rèn)的HeapDumper动壤,為AndroidHeapDumper萝喘。
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
// 保存WatchExecutor,如果沒(méi)有定義琼懊,則使用默認(rèn)的WatchExecutor阁簸,為AndroidWatchExecutor。
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
// 保存GcTrigger哼丈,如果沒(méi)有定義启妹,則使用默認(rèn)的GcTrigger,為GcTrigger.DEFAULT
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);//調(diào)用RefWatcher的構(gòu)造方法醉旦,保留設(shè)置的參數(shù)
}
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
this.heapDumper = checkNotNull(heapDumper, "heapDumper");
this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
retainedKeys = new CopyOnWriteArraySet<>();//保存key的集合
queue = new ReferenceQueue<>();//引用隊(duì)列
}
可以看到build()方法主要是根據(jù)之前設(shè)置的一些參數(shù)來(lái)構(gòu)建RefWatcher對(duì)象饶米,如果有參數(shù)沒(méi)有初始化,則使用默認(rèn)的配置參數(shù)來(lái)初始化车胡。在RefWatcher中檬输,還有兩個(gè)比較關(guān)鍵的成員,分別是retainedKeys和queue匈棘,他們?cè)诤竺媾袛嘁粋€(gè)對(duì)象是否泄漏將會(huì)被用到丧慈。
3.6 ActivityRefWatcher.install()
/*
* 注冊(cè)監(jiān)聽
*/
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();//見3.7
}
該方法首先是創(chuàng)建一個(gè)ActivityRefWatcher對(duì)象,該對(duì)象用來(lái)確保Activity被銷毀的時(shí)候不會(huì)泄漏主卫。接著調(diào)用watchActivities()來(lái)監(jiān)控Activity實(shí)例是否泄漏了伊滋。
public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = checkNotNull(application, "application");
this.refWatcher = checkNotNull(refWatcher, "refWatcher");
}
3.7 ActivityRefWatcher.watchActivities()
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();//避免兩次重復(fù)注冊(cè)
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);//調(diào)用Application的registerActivityLifecycleCallbacks()方法,該方法只在Android 4.0以后才支持队秩,見3.8
}
watchActivities()方法主要是調(diào)用Application的registerActivityLifecycleCallbacks()方法來(lái)注冊(cè)生命周期回調(diào)監(jiān)聽,在Android 4.0以后昼浦,Application才提供registerActivityLifecycleCallbacks()方法來(lái)注冊(cè)監(jiān)聽Activity的生命周期函數(shù)馍资,因此在Android 4.0以后,則可以通過(guò)LeakCanary.install(Context)就完成注冊(cè)監(jiān)聽過(guò)程关噪。如果是Android 4.0以前鸟蟹,則需要先獲取RefWatcher對(duì)象,然后在Activity的onDestroyed()方法里使兔,主要去調(diào)用監(jiān)聽RefWatcher.watch()方法建钥。
3.8 Application.registerActivityLifecycleCallbacks()
/*
* 注冊(cè)Activity生命周期方法調(diào)用回調(diào)接口
*/
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.add(callback);
}
}
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);//Activity執(zhí)行onCreate()時(shí)回調(diào)
void onActivityStarted(Activity activity);//Activity執(zhí)行onStart()時(shí)回調(diào)
void onActivityResumed(Activity activity);//Activity執(zhí)行onResume()時(shí)回調(diào)
void onActivityPaused(Activity activity);//Activity執(zhí)行onPause()時(shí)回調(diào)
void onActivityStopped(Activity activity);//Activity執(zhí)行onStop()時(shí)回調(diào)
void onActivitySaveInstanceState(Activity activity, Bundle outState);//Activity執(zhí)行onSaveInstanceState(()時(shí)回調(diào)
void onActivityDestroyed(Activity activity);//Activity執(zhí)行onDestroy()時(shí)回調(diào)
}
在ActivityRefWatcher中,定義了ActivityLifecycleCallbacks回調(diào)接口虐沥,具體如下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);//見3.9
}
};
可以看到熊经,在ActivityRefWatcher中的定義的lifecycleCallbacks泽艘,只實(shí)現(xiàn)了onActivityDestroyed的周期回調(diào)方法,該方法是在Activity執(zhí)行onDestroy()方法時(shí)镐依,被回掉的匹涮。因此ActivityRefWatcher是在Activity被銷毀時(shí),開始工作的槐壳,監(jiān)控Activity是否被銷毀了然低。
3.9 ActivityRefWatcher.onActivityDestroyed()
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
可以看到,最終的監(jiān)控還是通過(guò)RefWatcher的watch()的方法來(lái)監(jiān)控是否發(fā)生了泄漏务唐。監(jiān)控的時(shí)機(jī)是Activity被銷毀的時(shí)候雳攘,此時(shí)開始監(jiān)控該引用對(duì)象是否被回收了,從而判斷是否泄漏了枫笛。
小結(jié)
從LeakCanary的install()方法可以看到吨灭,它主要初始化一些參數(shù),為監(jiān)控對(duì)象是否發(fā)生了泄漏做準(zhǔn)備工作崇堰。其主要的工作有:
- 通過(guò)Builder模式沃于,構(gòu)建一個(gè)RefWatcher對(duì)象,RefWatcher對(duì)象里面包含了很多內(nèi)存泄漏相關(guān)的輔助類海诲。
- 通過(guò)Application的registerActivityLifecycleCallbacks()方法注冊(cè)生命周期回調(diào)接口繁莹,可以讓RefWatcher在Activity被銷毀的時(shí)候,開始監(jiān)控Activity引用是否發(fā)生了內(nèi)存泄漏。
需要說(shuō)明的是特幔,Application的registerActivityLifecycleCallbacks()方法是在Android 4.0以后才提供的咨演,因此在Android 4.0以后,可以直接使用LeakCanary.install()來(lái)監(jiān)聽Activity實(shí)例是否泄漏了蚯斯。如果是Android 4.0以前薄风,或者是需要監(jiān)聽其他有生命周期的對(duì)象是否發(fā)生了內(nèi)存泄漏,則需要先通過(guò)LeakCanary.install()方法返回一個(gè)RefWatcher對(duì)象拍嵌,然后通過(guò)RefWatcher對(duì)象的watch()方法來(lái)監(jiān)控對(duì)象是否發(fā)生了內(nèi)存泄漏遭赂。例如:
RefWatcher refWatcher = LeakCanary.install(context);
// 監(jiān)控
refWatcher.watch(objReference);
第二部分:監(jiān)控內(nèi)存泄漏
先來(lái)看下LeakCanary監(jiān)控內(nèi)存泄漏的時(shí)序圖:
從上面的分析可以知道,內(nèi)存泄漏監(jiān)控是通過(guò)RefWatcher的watch()方法開始的横辆,因此撇他,接下來(lái)將從RefWatcher的watch()方法進(jìn)行分析監(jiān)控內(nèi)存泄漏的原理。
4.1 RefWatcher.watch()
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/*
* 監(jiān)控提供的引用對(duì)象狈蚤,檢查它是否能被垃圾回收困肩;
* 該方法是非阻塞的,檢查操作是由WatchExecutor執(zhí)行器來(lái)執(zhí)行異步檢查
*/
public void watch(Object watchedReference, String referenceName) {
// 如果RefWatcher被禁用了脆侮,則直接返回
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();//記錄開始監(jiān)控的時(shí)間
String key = UUID.randomUUID().toString();//生成一個(gè)唯一的key锌畸,后續(xù)判斷一個(gè)對(duì)象是否泄漏了需要使用到該可以
retainedKeys.add(key);//將可以添加到一個(gè)set集合中,retainedKeys在初始化RefWatcher的時(shí)候構(gòu)建的靖避,可以見3.5
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//構(gòu)建一個(gè)KeyedWeakReference對(duì)象潭枣,見4.2
ensureGoneAsync(watchStartNanoTime, reference);//異步檢查引用對(duì)象是否被回收了比默,見4.3
}
在RefWatcher的watch()方法中:
- 首先隨機(jī)生成一個(gè)唯一的key,這個(gè)key用來(lái)在后面確定一個(gè)對(duì)象是否泄漏了卸耘;
- 接著創(chuàng)建一個(gè)KeyedWeakReference對(duì)象退敦,該對(duì)象包含了key,引用對(duì)象隊(duì)列queue以及引用對(duì)象蚣抗,KeyedWeakReference繼承了WeakReference侈百,也是弱引用對(duì)象;
- 最后調(diào)用異步方法ensureGoneAsync()檢查引用對(duì)象是否被回收了翰铡,從而判斷是否發(fā)生了內(nèi)存泄漏钝域;
4.2 KeyedWeakReference()
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");//保存key
this.name = checkNotNull(name, "name");
}
}
KeyedWeakReference繼承了WeakReference類,調(diào)用WeakReference類的構(gòu)造方法將引用對(duì)象referent和引用隊(duì)列referenceQueue傳遞給WeakReference锭魔。
/*
* 創(chuàng)建一個(gè)新的弱引用例证,該弱引用指向指定的對(duì)象和注冊(cè)到引用對(duì)象隊(duì)列中
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);//調(diào)用Reference的構(gòu)造方法
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
引用(Reference)為垃圾收集提供了更大的靈活性,根據(jù)不同的適用場(chǎng)景迷捧,引用可以分為4類:
- 強(qiáng)引用(Strong Reference)
強(qiáng)引用就是指在程序代碼中普遍存在的织咧,類似于“Object obj = new Object()”這類的引用,只要強(qiáng)引用還存在漠秋,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象笙蒙;
- 軟引用(Soft Reference)——SoftReference
軟引用是用來(lái)描述一些還有用但并非必需的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象庆锦,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前捅位,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒(méi)有足夠的內(nèi)存搂抒,才會(huì)拋出內(nèi)存溢出異常艇搀。(只有內(nèi)存不足時(shí),才會(huì)回收軟引用對(duì)象)求晶。
- 弱引用(Weak Reference)——WeakReference
弱引用也是用來(lái)描述非必需對(duì)象的焰雕,但它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前芳杏。當(dāng)垃圾收集器工作時(shí)淀散,無(wú)論當(dāng)前內(nèi)存釋放足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象蚜锨。(只要發(fā)生了垃圾收集,弱引用對(duì)象都會(huì)被回收)
- 虛引用(Phantom Reference)——PhantomReference
虛引用也稱為幽靈引用或者幻影引用慢蜓,它是最弱的一種引用關(guān)系亚再。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響晨抡,也無(wú)法通過(guò)虛引用獲取一個(gè)對(duì)象實(shí)例氛悬。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知则剃。
當(dāng)使用弱引用(WeakReference)時(shí),只要發(fā)生了垃圾回收GC如捅,則不管內(nèi)存是否充足時(shí)棍现,都會(huì)將該對(duì)象進(jìn)行回收。如果WeakReference定義了引用隊(duì)列(ReferenceQueue)镜遣,則還會(huì)將這個(gè)WeakReference放入與之關(guān)聯(lián)的ReferenceQueue中己肮。因此,每次KeyedWeakReference對(duì)象被回收時(shí)悲关,都會(huì)被放入到與之關(guān)聯(lián)的引用隊(duì)列queue中谎僻。
KeyedWeakReference類是一個(gè)弱引用,指向了需要監(jiān)控的引用對(duì)象watchedReference寓辱,并且還關(guān)聯(lián)了一個(gè)ReferenceQueue引用隊(duì)列艘绍,這個(gè)引用隊(duì)列是在構(gòu)建RefWatcher類的時(shí)候創(chuàng)建的,保存在queue變量中秫筏。另外诱鞠,在KeyedWeakReference類中,還保存了一個(gè)key这敬,這個(gè)key是與被監(jiān)控的引用對(duì)象watchedReference相關(guān)聯(lián)的航夺,因?yàn)檫@個(gè)key是一個(gè)隨機(jī)生成的一個(gè)唯一的key,因此可以通過(guò)key定位到引用對(duì)象watchedReference鹅颊。
4.3 RefWatcher.ensureGoneAsync()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {//見4.4
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);//見4.9
}
});
}
Retryable是一個(gè)接口敷存,定義了一個(gè)可以重試的工作單元。
public interface Retryable {
enum Result {
DONE, RETRY
}
Result run();
}
WatchExecutor也是一個(gè)接口堪伍,里面定義了execute()方法锚烦,主要是用來(lái)執(zhí)行Retryable。
public interface WatchExecutor {
//空實(shí)現(xiàn)
WatchExecutor NONE = new WatchExecutor() {
@Override
public void execute(Retryable retryable) {
}
};
void execute(Retryable retryable);//定義了execute()接口
}
WatchExecutor在構(gòu)建RefWatcher的時(shí)候被初始化了帝雇,默認(rèn)被初始化為AndroidWatchExecutor涮俄。
@Override
protected WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());//主線程的Handler
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();//啟動(dòng)一個(gè)LeakCanary-Heap-Dump線程
backgroundHandler = new Handler(handlerThread.getLooper());//子線程Handler
this.initialDelayMillis = initialDelayMillis;//初始化延遲時(shí)間,默認(rèn)為5s
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
在AndroidWatchExecutor中初始化了兩個(gè)Handler尸闸,一個(gè)是主線程相關(guān)的Handler彻亲,另外是一個(gè)子線程的Handler。
4.4 AndroidWatchExecutor.execute()
@Override
public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);// 如果是主線程吮廉,則等待消息隊(duì)列空閑時(shí)調(diào)用苞尝,見4.5
} else {
postWaitForIdle(retryable, 0);// 如果是子線程,則延遲指定延遲執(zhí)行宦芦,見4.8
}
}
AndroidWatchExecutor會(huì)根據(jù)線程是在主線程還是子線程宙址,決定調(diào)用waitForIdle()還是postWaitForIdle()方法。
因?yàn)樵谥骶€程的話调卑,需要等待消息隊(duì)列空閑的時(shí)候才會(huì)調(diào)用抡砂,這樣不會(huì)干擾應(yīng)用的正常運(yùn)行大咱。如果是在子線程中,則可以等待一段時(shí)間后再執(zhí)行注益,不管是通過(guò)哪種方法碴巾,最后都是通過(guò)子線程延遲一段時(shí)間來(lái)執(zhí)行。
4.5 AndroidWatchExecutor.waitForIdle()
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
// 創(chuàng)建一個(gè)IdleHandler丑搔,將其放入MessageQueue中
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);//見4.6
return false;//返回false厦瓢,移除該IdleHandler,確保只執(zhí)行一次
}
});
}
當(dāng)通過(guò)MessageQueue注冊(cè)了一個(gè)IdleHandler低匙,那么當(dāng)消息隊(duì)列空閑時(shí)旷痕,則會(huì)回調(diào)其queueIdle()方法,然后調(diào)用postToBackgroundWithDelay()方法延遲執(zhí)行顽冶。
4.6 AndroidWatchExecutor.postToBackgroundWithDelay()
void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);//指數(shù)回退因素
long delayMillis = initialDelayMillis * exponentialBackoffFactor;//延遲時(shí)間
backgroundHandler.postDelayed(new Runnable() {//將消息放入后臺(tái)線程中執(zhí)行
@Override
public void run() {
Retryable.Result result = retryable.run();//調(diào)用Retryable的run方法
// 如果是需要重試的話欺抗,則嘗試再次等待主線程空閑時(shí),再由子線程嘗試執(zhí)行
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
在postToBackgroundWithDelay()方法中强重,主要是計(jì)算延遲執(zhí)行時(shí)間绞呈,然后執(zhí)行Retryable中定義的run()方法,如果執(zhí)行結(jié)果是需要重新執(zhí)行的話间景,則再通過(guò)postWaitForIdle()方法佃声,等待主線程空閑后,調(diào)用子線程延遲執(zhí)行倘要。
每次重試延遲的時(shí)間都不一樣圾亏,延遲時(shí)間是呈指數(shù)增長(zhǎng)的,例如第一次延遲時(shí)間為5s = 1 * 5封拧,那么第二次延遲的時(shí)間就為10s = 2 * 5志鹃,第三次延遲時(shí)間就是為20s = 4 * 5;
4.7 AndroidWatchExecutor.postWaitForIdle()
void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override
public void run() {
waitForIdle(retryable, failedAttempts);//等待主線程空閑后,調(diào)用子線程延遲執(zhí)行泽西。
}
});
}
可以看到曹铃,postWaitForIdle()方法,主要是向主線程的消息隊(duì)列發(fā)送一個(gè)消息捧杉,然后等待主線程的消息隊(duì)列空閑陕见,當(dāng)主線程消息隊(duì)列空閑時(shí),則調(diào)用子線程延遲一段時(shí)間來(lái)執(zhí)行任務(wù)味抖。
回調(diào)4.3中评甜,我們可以知道,ensureGone()方法是在子線程中執(zhí)行仔涩,并且根據(jù)執(zhí)行結(jié)果來(lái)決定是否需要稍后重試執(zhí)行忍坷,并且稍后重試執(zhí)行的時(shí)間每次都不一樣。
4.8 RefWatcher.ensureGone()
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();// GC開始的時(shí)間
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//記錄監(jiān)控的時(shí)長(zhǎng)
removeWeaklyReachableReferences();//嘗試從集合中移除弱引用,見4.9
// 如果是在調(diào)試承匣,則直接返回
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {//判斷引用是否被回收了,見4.10
return DONE;
}
gcTrigger.runGc();//主動(dòng)觸發(fā)垃圾回收锤悄,見4.11
removeWeaklyReachableReferences();//再一次嘗試從集合中移除弱引用
if (!gone(reference)) {//弱引用沒(méi)有被回收韧骗,說(shuō)明發(fā)生了內(nèi)存泄漏
long startDumpHeap = System.nanoTime();//記錄Dump Heap的開始時(shí)間
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);//記錄GC所花費(fèi)的時(shí)間
File heapDumpFile = heapDumper.dumpHeap();//開始Dump Heap,見4.12
if (heapDumpFile == RETRY_LATER) {//如果當(dāng)前不能Dump Heap零聚,則稍后重試
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);//記錄Dump Heap所花費(fèi)的時(shí)間
// 分析Dump Heap的結(jié)果
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));//見4.15
}
return DONE;//返回執(zhí)行完成
}
ensureGone()方法是監(jiān)控內(nèi)存泄漏的關(guān)鍵方法袍暴,其主要的邏輯如下:
- 記錄GC開始的時(shí)間,并且計(jì)算監(jiān)控了多長(zhǎng)時(shí)間隶症;
- 第一次調(diào)用removeWeaklyReachableReferences()方法政模,嘗試從集合中移除弱引用,如果弱引用在引用隊(duì)列中蚂会,則說(shuō)明弱引用對(duì)象被回收了淋样;
- 第一次調(diào)用gone()方法,判斷弱引用是否被回收了胁住,如果被回收了趁猴,則說(shuō)明沒(méi)有發(fā)生內(nèi)存泄漏,執(zhí)行完成彪见,直接返回儡司;
- 如果判斷弱引用還沒(méi)有被回收,則調(diào)用gcTrigger.runGc()方法主動(dòng)觸發(fā)一次GC操作余指;
- 第二次調(diào)用removeWeaklyReachableReferences()方法捕犬,再次嘗試從集合中移除弱引用;
- 第二次調(diào)用gone()方法酵镜,判斷弱引用是否被回收了碉碉;
- 如果弱引用對(duì)象沒(méi)有被回收,則說(shuō)明了發(fā)生了內(nèi)存泄漏笋婿,則開始處理內(nèi)存泄漏的流程:
- 記錄堆轉(zhuǎn)儲(chǔ)(Dump Heap)的起始時(shí)間誉裆,以及GC所花費(fèi)的時(shí)間;
- 獲取堆轉(zhuǎn)儲(chǔ)文件HeapDumpFile;
- 記錄堆轉(zhuǎn)儲(chǔ)所花費(fèi)的時(shí)間缸濒;
- 調(diào)用heapdumpListener中的analyze()方法來(lái)分析堆轉(zhuǎn)儲(chǔ)文件HeapDumpFile足丢;
可以看到,關(guān)鍵是通過(guò)判斷引用隊(duì)列中是否有弱引用對(duì)象庇配,來(lái)判斷是否發(fā)生了內(nèi)存泄漏斩跌。如果發(fā)生了內(nèi)存泄漏,則通過(guò)Dump Heap來(lái)獲取堆轉(zhuǎn)儲(chǔ)文件捞慌,然后再通過(guò)分析堆轉(zhuǎn)儲(chǔ)文件來(lái)得出內(nèi)存泄漏的引用鏈路耀鸦。
4.9 RefWatcher.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.
KeyedWeakReference ref;
// 當(dāng)弱引用對(duì)象可以被回收時(shí),它會(huì)被放入引用隊(duì)列中
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);//根據(jù)key值移除set集合中存在的對(duì)應(yīng)key
}
}
在該方法中,首先從引用隊(duì)列中獲取一個(gè)弱引用對(duì)象袖订,如果存在弱引用對(duì)象氮帐,則說(shuō)明弱引用對(duì)象指向的引用對(duì)象是可以被回收的。因?yàn)閞etainedKeys是一個(gè)set集合洛姑,在構(gòu)建RefWatcher類的時(shí)候進(jìn)行了初始化上沐,并且在調(diào)用RefWatcher的watch()方法時(shí)將需要監(jiān)控的引用對(duì)象對(duì)應(yīng)的key添加到retainedKeys集合中。因此楞艾,在調(diào)用retainedKeys的remove()方法之前参咙,該引用對(duì)象對(duì)應(yīng)的key將一直存在。當(dāng)引用隊(duì)列中存在弱引用對(duì)象硫眯,則將在retainedKeys中移除該弱引用對(duì)象對(duì)應(yīng)的key蕴侧。
我們可以把retainedKeys集合看作是還沒(méi)有被垃圾回收的引用對(duì)象的key集合,因此接下來(lái)的gone()方法主要是通過(guò)判斷
retainedKey集合中是否還保存有引用對(duì)象對(duì)應(yīng)的key两入。
4.10 RefWatcher.gone()
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
gone()方法主要是判斷retainedKeys集合中是否還包含引用對(duì)象對(duì)應(yīng)的key净宵,如果不存在,則說(shuō)明該引用對(duì)象以及被回收了谆刨,沒(méi)有發(fā)生內(nèi)存泄漏塘娶。如果存在,則說(shuō)明發(fā)生了內(nèi)存泄漏痊夭。
4.11 GcTrigger.gc()
public interface GcTrigger {
// 默認(rèn)實(shí)現(xiàn)
GcTrigger DEFAULT = new GcTrigger() {
@Override
public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();//調(diào)用Runtime.gc()來(lái)執(zhí)行GC操作
enqueueReferences();//等待100ms中刁岸,等待弱引用對(duì)象進(jìn)入引用隊(duì)列中
System.runFinalization();//執(zhí)行對(duì)象的finalize()方法
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}
GcTrigger是一個(gè)接口類,里面定義而來(lái)runGc()方法她我,GcTrigger接口里面還定義了一個(gè)默認(rèn)實(shí)現(xiàn)DEFAULT虹曙。通過(guò)GcTrigger的runGc()方法可以主動(dòng)觸發(fā)GC操作,這樣可以被回收的弱引用對(duì)象將會(huì)被放入引用隊(duì)列中番舆,這樣后續(xù)就可以檢查引用隊(duì)列來(lái)判斷是否回收成功了酝碳。
在GcTrigger的默認(rèn)實(shí)現(xiàn)中,是通過(guò)Runtime.gc()方法將執(zhí)行垃圾回收的恨狈,因?yàn)镾ystem.gc()不能保證每次都執(zhí)行垃圾收集疏哗。另外,為了確保弱引用對(duì)象被放入引用隊(duì)列中禾怠,需要每次垃圾收集后返奉,等待100ms,讓弱引用有足夠時(shí)間放入到引用隊(duì)列中吗氏。最后在通過(guò)System.runFinalization()方法執(zhí)行引用對(duì)象的finalize()方法芽偏。
4.12 AndroidHeapDumper.dumpHeap()
@Override
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();//創(chuàng)建一個(gè)dump文件,文件名是以_pending.hporf為結(jié)尾的
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;//稍后重試
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);//發(fā)送一個(gè)Dump Heap的等待Toast提示弦讽,見4.13
if (!waitingForToast.wait(5, SECONDS)) {//等待5s種污尉,如果5s內(nèi)沒(méi)有收到Toast,則說(shuō)明當(dāng)前還不能Dump Heap,需要稍后重試被碗。
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();//獲取Toast結(jié)果
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());//調(diào)用Debug類的dumpHprofData()方法某宪,來(lái)Dump Heap,見4.14
cancelToast(toast);//取消通知
return heapDumpFile;// 返回HeapDumpFile文件
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
heapDumper是在構(gòu)造RefWatcher的時(shí)候初始化的锐朴,默認(rèn)使用的AndroidHeapDumper類缩抡,通過(guò)defaultHeapDumper()方法來(lái)獲取的。
@Override
protected HeapDumper defaultHeapDumper() {
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
return new AndroidHeapDumper(context, leakDirectoryProvider);
}
其中LeakDirectoryProvider主要是負(fù)責(zé)Dump Heap文件的創(chuàng)建以及文件的命名等工作包颁。所有Dump Heap文件都是以_pending.hporf為結(jié)尾的,并且最多可以存儲(chǔ)7個(gè)Dump Heap文件压真。
在開始執(zhí)行dumpHeap()之前娩嚼,會(huì)先通過(guò)發(fā)送一個(gè)Toast提示需要執(zhí)行Dump Heap的操作,待主線程的消息隊(duì)列空閑后滴肿,在將結(jié)果通知子線程岳悟,可以開始Dump Heap操作,子線程會(huì)等待5s中泼差,等待主線程通知是否可以Dump Heap操作贵少。具體是showToast()方法和FutureResult來(lái)實(shí)現(xiàn)的戈锻。
4.13 AndroidHeapDumper.showToast()
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override
public void run() {
final Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(context);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
toast.show();//顯示Toast蒙兰,提示等待Dump Heap操作
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
waitingForToast.set(toast);//當(dāng)主線程的消息隊(duì)列空閑時(shí)囊蓝,則將結(jié)果通知給子線程龙宏,具體是通過(guò)FutureResult來(lái)實(shí)現(xiàn)的
return false;
}
});
}
});
}
在showToast()方法中作箍,主要是發(fā)送一個(gè)Toast提示需要等待執(zhí)行Dump Heap操作姿锭,當(dāng)主線程的消息隊(duì)列空閑時(shí)盆偿,則將此結(jié)果通知給主線程撕阎,具體是通過(guò)FutureResult來(lái)實(shí)現(xiàn)的缀皱。
FutureResult主要是通過(guò)閉鎖CountDownLatch來(lái)協(xié)調(diào)主線程與子線程間的同步操作斗这。FutureResult類的定義如下:
public final class FutureResult<T> {
private final AtomicReference<T> resultHolder;//原子引用
private final CountDownLatch latch;//閉鎖
public FutureResult() {
resultHolder = new AtomicReference<>();
latch = new CountDownLatch(1);//初始化值為1
}
public boolean wait(long timeout, TimeUnit unit) {
try {
return latch.await(timeout, unit);//在閉鎖上超時(shí)等待
} catch (InterruptedException e) {
throw new RuntimeException("Did not expect thread to be interrupted", e);
}
}
public T get() {
if (latch.getCount() > 0) {
throw new IllegalStateException("Call wait() and check its result");
}
return resultHolder.get();//獲取原子引用指向的對(duì)象
}
public void set(T result) {
resultHolder.set(result);//用原子引用指向?qū)ο? latch.countDown();//閉鎖計(jì)數(shù)減1
}
}
可以看到,在dumpHeap()的過(guò)程中啤斗,首先構(gòu)建一個(gè)FutureResult對(duì)象表箭,該對(duì)象指向Toast對(duì)象,當(dāng)調(diào)用showToast()方法時(shí)钮莲,會(huì)在主線程空閑時(shí)免钻,通過(guò)FutureResult.set()方法來(lái)保存指向的引用對(duì)象,并且將閉鎖計(jì)數(shù)減1臂痕,這樣阻塞在FutureResult.wait()方法的子線程伯襟,將會(huì)被喚醒。后續(xù)就可以通過(guò)FutureResult.get()方法來(lái)獲取保存的Toast對(duì)象握童,并通過(guò)cancelToast()方法來(lái)取消通知姆怪。
4.14 Debug.dumpHprofData()
public static void dumpHprofData(String fileName) throws IOException {
VMDebug.dumpHprofData(fileName);
}
Dump Heap操作主要是通過(guò)Debug類的dumpHprofData()方法來(lái)完成的,把Dump出來(lái)的數(shù)據(jù)保存在一個(gè)hprof文件中。
可以看到稽揭,dumpHeap()操作主要是借助系統(tǒng)的Debug類將內(nèi)存快照Dump到一個(gè)hprof文件中俺附。接下來(lái)看看,如何來(lái)解析hprof文件溪掀。
4.15 ServiceHeapDumpListener.analyze()
@Override
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);//調(diào)用HeapAnalyzerService服務(wù)的runAnalysis方法事镣,見4.16
}
heapdumpListener是在構(gòu)建RefWatcher類的時(shí)候初始化的,默認(rèn)實(shí)現(xiàn)是ServiceHeapDumpListener揪胃。
@Override protected HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabled(context, listenerServiceClass, true);
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");//保存了解析結(jié)果處理類
this.context = checkNotNull(context, "context").getApplicationContext();
}
在analyze方法中璃哟,解析的是一個(gè)HeapDump對(duì)象,該對(duì)象包含了前面操作中收集的各種參數(shù)喊递。例如Heap Dump文件随闪,引用對(duì)象對(duì)應(yīng)的key,忽略的引用對(duì)象列表骚勘,以及監(jiān)控時(shí)間铐伴、垃圾收集時(shí)間和Dump Heap時(shí)間。
public HeapDump(File heapDumpFile, String referenceKey, String referenceName,
ExcludedRefs excludedRefs, long watchDurationMs, long gcDurationMs, long heapDumpDurationMs) {
this.heapDumpFile = checkNotNull(heapDumpFile, "heapDumpFile");
this.referenceKey = checkNotNull(referenceKey, "referenceKey");
this.referenceName = checkNotNull(referenceName, "referenceName");
this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
this.watchDurationMs = watchDurationMs;
this.gcDurationMs = gcDurationMs;
this.heapDumpDurationMs = heapDumpDurationMs;
}
analyze()方法將調(diào)用HeapAnalyzerService類的runAnalysis()來(lái)執(zhí)行具體的解析hprof文件操作俏讹。
4.16 HeapAnalyzerService.runAnalysis()
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);
}
HeapAnalyzerService類繼承自IntentService類当宴,實(shí)現(xiàn)了onHandleIntent()方法。在runAnalysis()方法中主要是啟動(dòng)其自身服務(wù)HeapAnalyzerService泽疆,并將參數(shù)listenerServiceClass和heapDump傳遞過(guò)程户矢。IntentService的onHandleIntent()方法是在子線程中執(zhí)行的,因此解析hprof文件也是在子線程中執(zhí)行的殉疼。
4.17 HeapAnalyzerService.onHandleIntent()
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);//創(chuàng)建一個(gè)HeapAnalyzer逗嫡,來(lái)自Square的HaHa開源項(xiàng)目
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);//見4.18
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);//4.21
}
在子線程中,主要是借助Square開源項(xiàng)目的HaHa來(lái)解析hprof文件株依。具體是通過(guò)創(chuàng)建一個(gè)heapAnalyzer來(lái)解析hprof文件驱证。
4.18 HeapAnalyzer.checkForLeak()
/*
* 通過(guò)key來(lái)搜索對(duì)應(yīng)的KeyedWeakReference弱引用對(duì)象是否存在于 Heap Dump文件中,如果存在恋腕,則計(jì)算該引用對(duì)象到GC Roots的最短強(qiáng)引用路徑
*/
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();//記錄分析的起始時(shí)間
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();//把.hprof文件轉(zhuǎn)為Snapshot抹锄,這個(gè)Snapshot對(duì)象包含了對(duì)象引用的所有路徑
deduplicateGcRoots(snapshot);//去除重復(fù)的路徑
Instance leakingRef = findLeakingReference(referenceKey, snapshot);//找出泄漏的對(duì)象,見4.19
// False alarm, weak reference was cleared in between key check and heap dump.
// 如果沒(méi)有找到引用對(duì)象荠藤,則說(shuō)明沒(méi)有泄漏
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);//找出泄漏對(duì)象的最短路徑伙单,見4.20
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
可以看到,checkForLeak()方法的主要作用是解析.hprof哈肖,輸出結(jié)果吻育,主要分為以下幾個(gè)步驟:
- 把.hprof文件轉(zhuǎn)換為Snapshot,這個(gè)Snapshot包含了引用對(duì)象的所有路徑淤井;
- 去除重復(fù)的引用路徑布疼,減少解析hprof文件時(shí)的壓力摊趾;
- 找出泄漏的引用對(duì)象;
- 如果沒(méi)有找到泄漏的引用對(duì)象游两,則直接返回砾层;
- 如果存在泄漏的引用對(duì)象,則找出泄漏對(duì)象到GC Root的最短強(qiáng)引用路徑贱案;
4.19 HeapAnalyzer.findLeakingReference()
private Instance findLeakingReference(String key, Snapshot snapshot) {
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List<String> keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
String keyCandidate = asString(fieldValue(values, "key"));
if (keyCandidate.equals(key)) {//找到了肛炮,返回內(nèi)存泄漏對(duì)象
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
該方法的主要是在Snapshot快照中,查找所有的KeyedWeakReference弱引用對(duì)象實(shí)例宝踪,然后比較key值是否相等侨糟,如果與參數(shù)中傳遞的key相等,則說(shuō)明找到了內(nèi)存泄漏的對(duì)象瘩燥,并將該泄漏對(duì)象返回粟害。
4.20 HeapAnalyzer.findLeakTrace()
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//查找最短引用路徑
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {//如果不存在強(qiáng)引用路徑到GC Root,則說(shuō)明沒(méi)有內(nèi)存泄漏
return noLeak(since(analysisStartNanoTime));
}
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);//構(gòu)建對(duì)象泄漏路徑
String className = leakingRef.getClassObj().getClassName();//獲取泄漏對(duì)象的類名
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;//獲取泄漏對(duì)象的實(shí)例
long retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));//返回分析結(jié)果
}
在findLeakTrace()方法中颤芬,主要借助ShortestPathFinder類查找最短引用路徑,如果不存在最短引用路徑套鹅,則說(shuō)明沒(méi)有內(nèi)存泄漏站蝠。如果存在最短路徑,則將查找結(jié)果返回卓鹿。
hprof文件主要是借助開源項(xiàng)目HAHA來(lái)完成解析工作菱魔,并找出內(nèi)存泄漏對(duì)象到GC Root的最短引用路徑。當(dāng)HeapAnalyzer返回了解析結(jié)果后吟孙,在通過(guò)AbstractAnalysisResultService將解析結(jié)果發(fā)送給展示內(nèi)存泄漏分析結(jié)果的DisplayLeakService服務(wù)澜倦。
4.21 AbstractAnalysisResultService.sendResultToListener()
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);//通過(guò)startService()方法啟動(dòng)DisplayLeakService服務(wù)
}
listenerServiceClassName在調(diào)用LeakCanary的install()方法時(shí),傳遞了DisplayLeakService類名杰妓,保存在ServiceHeapDumpListener的listenerServiceClass成員變量中藻治,因此該方法主要是啟動(dòng)DisplayLeakService服務(wù)。DisplayLeakService服務(wù)繼承自AbstractAnalysisResultService類巷挥,而AbstractAnalysisResultService又繼承自IntentService服務(wù)桩卵,因此最終會(huì)調(diào)用到AbstractAnalysisResultService的onHandleIntent()方法。
4.22 AbstractAnalysisResultService.onHandleIntent()
@Override
protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);//調(diào)用DisplayLeakService類的onHeapAnalyzed方法
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
在AbstractAnalysisResultService的onHandleIntent()方法中倍宾,主要是調(diào)用onHeapAnalyzed()方法雏节,該方法是一個(gè)抽象方法,由AbstractAnalysisResultService的子類DisplayLeakService類來(lái)實(shí)現(xiàn)高职。
4.23 DisplayLeakService.onHeapAnalyzed()
@Override
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);//構(gòu)造內(nèi)存泄漏信息
CanaryLog.d("%s", leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);//保存內(nèi)存泄漏結(jié)果
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {//沒(méi)有內(nèi)存泄漏
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);//發(fā)送一個(gè)內(nèi)存泄漏的通知
afterDefaultHandling(heapDump, result, leakInfo);//子類可以覆寫該方法钩乍,來(lái)上傳內(nèi)存泄漏的hprof文件。
}
可以看到怔锌,在onHeapAnalyzed()方法中寥粹,主要是發(fā)送一個(gè)通知变过,告訴用戶內(nèi)存泄漏了,具體的內(nèi)存泄漏信息在DisplayLeakActivity中展示排作,最后調(diào)用了afterDefaultHandling()方法牵啦,該方法默認(rèn)是空實(shí)現(xiàn),用戶可以繼承DisplayLeakService類然后覆寫該方法妄痪,在該方法中實(shí)現(xiàn)將內(nèi)存泄漏的hprof文件上傳到網(wǎng)絡(luò)服務(wù)器上哈雏。
小結(jié)
監(jiān)控內(nèi)存泄漏可以分為思大部分:
- 一是檢測(cè)是否發(fā)生了內(nèi)存泄漏,具體是通過(guò)弱引用對(duì)象KeyedWeakReference指向需要被監(jiān)控的引用對(duì)象衫生,然后通過(guò)主動(dòng)GC來(lái)判斷該對(duì)象是否被回收了裳瘪,如果該引用對(duì)象被回收了,則在引用隊(duì)列中會(huì)保存該弱引用對(duì)象罪针,然后通過(guò)判斷引用隊(duì)列中是否存在該弱引用對(duì)象來(lái)得出是否發(fā)生了內(nèi)存泄漏彭羹。具體的過(guò)程見4.1~4.12。
- 二是生成Dump Heap文件泪酱,具體是通過(guò)Debug類的dumpHprofData()方法來(lái)生成hprof格式的堆轉(zhuǎn)儲(chǔ)文件派殷。具體的過(guò)程見4.13~4.15。
- 三是解析hprof文件墓阀,找出內(nèi)存泄漏對(duì)象到GC Roots的最短引用路徑毡惜。具體是通過(guò)開源項(xiàng)目HaHa來(lái)完成解析工作,具體的過(guò)程見4.16~4.21斯撮。
- 四是將內(nèi)存泄漏解析結(jié)果展示給用戶经伙,具體是通過(guò)DisplayLeakService服務(wù)將內(nèi)存泄漏結(jié)果以通知的形式發(fā)送給用戶,并且在DisplayLeakActivity中展示內(nèi)存泄漏信息勿锅。具體過(guò)程見4.22~4.24帕膜。
4.LeakCanary類圖
5.總結(jié)
LeakCanary的使用非常簡(jiǎn)單,只要通過(guò)LeakCanary的install()方法溢十,獲取RefWatcher對(duì)象垮刹,然后調(diào)用RefWatcher的watch()方法進(jìn)行監(jiān)控需要被觀察的引用對(duì)象。如果只是需要監(jiān)控Activity引用對(duì)象张弛,則更簡(jiǎn)單危纫,只需在Application的onCreate()的方法中調(diào)用LeakCanary.install()方法,就可以完成監(jiān)控操作(備注:針對(duì)Android 4.0以上乌庶,如果是Android 4.0以下种蝶,則還需要主動(dòng)調(diào)用RefWatcher的watch()方法進(jìn)行監(jiān)控)。
RefWatcher監(jiān)控內(nèi)存泄漏的原理主要是借助弱引用對(duì)象KeyedWeakReference與被監(jiān)控的引用對(duì)象進(jìn)行關(guān)聯(lián)瞒大,當(dāng)被監(jiān)控的引用對(duì)象被回收時(shí)螃征,則與之關(guān)聯(lián)的弱引用對(duì)象也會(huì)被回收,并放入到一個(gè)引用隊(duì)列中透敌。通過(guò)在引用隊(duì)列中查找是否存在該引用對(duì)象盯滚,來(lái)判斷對(duì)象是否泄漏了踢械。
當(dāng)對(duì)象發(fā)生了內(nèi)存泄漏,則通過(guò)Debug類的dumpHprofData()方法獲取堆內(nèi)存快照hprof文件魄藕,然后通過(guò)開源項(xiàng)目HaHa分析hprof文件内列,并將分析結(jié)果(內(nèi)存泄漏對(duì)象到GC Roots的最短引用鏈路)通過(guò)DisplayLeakService服務(wù)發(fā)送給用戶查看。
內(nèi)存泄漏的監(jiān)控過(guò)程都是在異步線程中處理的背率,主要體現(xiàn)在以下幾個(gè)方面:
- 在RefWatcher.ensureGoneAsync()方法中话瞧,如果是在主線程,則等到主線程消息隊(duì)列空閑時(shí)寝姿,再由子線程延遲執(zhí)行交排;如果是在子線程中,則直接由子線程延遲執(zhí)行饵筑。每次延遲的時(shí)間都不一樣埃篓,回退的時(shí)間呈指數(shù)增長(zhǎng)。在該方法中根资,主要是檢測(cè)是否發(fā)生了內(nèi)存泄漏架专。
- AndroidHeapDumper.dumpHeap()操作是在子線程執(zhí)行的,該方法也是等到主線程消息隊(duì)列空閑后才執(zhí)行的玄帕,具體是通過(guò)閉鎖CountDownLatch來(lái)協(xié)調(diào)主線程與子線程間的同步操作部脚。在該方法中,主要是Dump內(nèi)存快照桨仿,生成hprof文件。
- HeapAnalyzerService.runAnalysis()操作是在子線程中執(zhí)行的案狠,因?yàn)镠eapAnalyzerService繼承了IntentService服務(wù)服傍,在onHandleIntent()方法中執(zhí)行具體的解析hprof文件操作。
- DisplayLeakService.onHeapAnalyzed()操作是在線程中執(zhí)行的骂铁,因?yàn)镈isplayLeakService繼承了AbstractAnalysisResultService服務(wù)吹零,而AbstractAnalysisResultService服務(wù)又繼承了IntentService,并實(shí)現(xiàn)了onHandleIntent()方法拉庵。在onHeapAnalyzed()方法中灿椅,主要是發(fā)送一個(gè)通知告訴用戶內(nèi)存泄漏,并將內(nèi)存泄漏結(jié)果在DisplayLeakActivity中展示钞支。
6.LeakCanary使用示例
Demo的使用示例:LeakDemo示例