LeakCanary原理淺析

LeakCanary原理淺析

1.LeakCanary簡(jiǎn)介

LeakCanary是一個(gè)Android和Java的內(nèi)存泄漏檢測(cè)庫(kù)变逃,可以大幅可以大幅度減少了開發(fā)中遇到的OOM問(wèn)題。

LeakCanary開源庫(kù)的地址為:

LeakCanary開源庫(kù)

LeakCanary的README地址如下:

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í)序圖:

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)備工作崇堰。其主要的工作有:

  1. 通過(guò)Builder模式沃于,構(gòu)建一個(gè)RefWatcher對(duì)象,RefWatcher對(duì)象里面包含了很多內(nèi)存泄漏相關(guān)的輔助類海诲。
  2. 通過(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í)序圖:

LeakCanary監(jiān)控內(nèi)存泄漏

從上面的分析可以知道,內(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()方法中:

  1. 首先隨機(jī)生成一個(gè)唯一的key,這個(gè)key用來(lái)在后面確定一個(gè)對(duì)象是否泄漏了卸耘;
  2. 接著創(chuàng)建一個(gè)KeyedWeakReference對(duì)象退敦,該對(duì)象包含了key,引用對(duì)象隊(duì)列queue以及引用對(duì)象蚣抗,KeyedWeakReference繼承了WeakReference侈百,也是弱引用對(duì)象;
  3. 最后調(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)鍵方法袍暴,其主要的邏輯如下:

  1. 記錄GC開始的時(shí)間,并且計(jì)算監(jiān)控了多長(zhǎng)時(shí)間隶症;
  2. 第一次調(diào)用removeWeaklyReachableReferences()方法政模,嘗試從集合中移除弱引用,如果弱引用在引用隊(duì)列中蚂会,則說(shuō)明弱引用對(duì)象被回收了淋样;
  3. 第一次調(diào)用gone()方法,判斷弱引用是否被回收了胁住,如果被回收了趁猴,則說(shuō)明沒(méi)有發(fā)生內(nèi)存泄漏,執(zhí)行完成彪见,直接返回儡司;
  4. 如果判斷弱引用還沒(méi)有被回收,則調(diào)用gcTrigger.runGc()方法主動(dòng)觸發(fā)一次GC操作余指;
  5. 第二次調(diào)用removeWeaklyReachableReferences()方法捕犬,再次嘗試從集合中移除弱引用;
  6. 第二次調(diào)用gone()方法酵镜,判斷弱引用是否被回收了碉碉;
  7. 如果弱引用對(duì)象沒(méi)有被回收,則說(shuō)明了發(fā)生了內(nèi)存泄漏笋婿,則開始處理內(nèi)存泄漏的流程:
    1. 記錄堆轉(zhuǎn)儲(chǔ)(Dump Heap)的起始時(shí)間誉裆,以及GC所花費(fèi)的時(shí)間;
    2. 獲取堆轉(zhuǎn)儲(chǔ)文件HeapDumpFile;
    3. 記錄堆轉(zhuǎn)儲(chǔ)所花費(fèi)的時(shí)間缸濒;
    4. 調(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è)步驟:

  1. 把.hprof文件轉(zhuǎn)換為Snapshot,這個(gè)Snapshot包含了引用對(duì)象的所有路徑淤井;
  2. 去除重復(fù)的引用路徑布疼,減少解析hprof文件時(shí)的壓力摊趾;
  3. 找出泄漏的引用對(duì)象;
  4. 如果沒(méi)有找到泄漏的引用對(duì)象游两,則直接返回砾层;
  5. 如果存在泄漏的引用對(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類圖

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è)方面:

  1. 在RefWatcher.ensureGoneAsync()方法中话瞧,如果是在主線程,則等到主線程消息隊(duì)列空閑時(shí)寝姿,再由子線程延遲執(zhí)行交排;如果是在子線程中,則直接由子線程延遲執(zhí)行饵筑。每次延遲的時(shí)間都不一樣埃篓,回退的時(shí)間呈指數(shù)增長(zhǎng)。在該方法中根资,主要是檢測(cè)是否發(fā)生了內(nèi)存泄漏架专。
  2. AndroidHeapDumper.dumpHeap()操作是在子線程執(zhí)行的,該方法也是等到主線程消息隊(duì)列空閑后才執(zhí)行的玄帕,具體是通過(guò)閉鎖CountDownLatch來(lái)協(xié)調(diào)主線程與子線程間的同步操作部脚。在該方法中,主要是Dump內(nèi)存快照桨仿,生成hprof文件。
  3. HeapAnalyzerService.runAnalysis()操作是在子線程中執(zhí)行的案狠,因?yàn)镠eapAnalyzerService繼承了IntentService服務(wù)服傍,在onHandleIntent()方法中執(zhí)行具體的解析hprof文件操作。
  4. 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示例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茫蛹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烁挟,更是在濱河造成了極大的恐慌婴洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撼嗓,死亡現(xiàn)場(chǎng)離奇詭異柬采,居然都是意外死亡欢唾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門粉捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)礁遣,“玉大人,你說(shuō)我怎么就攤上這事肩刃∷罨簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵树酪,是天一觀的道長(zhǎng)浅碾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)续语,這世上最難降的妖魔是什么垂谢? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮疮茄,結(jié)果婚禮上滥朱,老公的妹妹穿的比我還像新娘。我一直安慰自己力试,他們只是感情好徙邻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畸裳,像睡著了一般缰犁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怖糊,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天帅容,我揣著相機(jī)與錄音,去河邊找鬼伍伤。 笑死并徘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扰魂。 我是一名探鬼主播麦乞,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劝评!你這毒婦竟也來(lái)了姐直?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒋畜,失蹤者是張志新(化名)和其女友劉穎简肴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體百侧,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砰识,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年能扒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辫狼。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡初斑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膨处,到底是詐尸還是另有隱情见秤,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布真椿,位于F島的核電站鹃答,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏突硝。R本人自食惡果不足惜测摔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望解恰。 院中可真熱鬧锋八,春花似錦、人聲如沸护盈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腐宋。三九已至紊服,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胸竞,已是汗流浹背欺嗤。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撤师,地道東北人剂府。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓拧揽,卻偏偏與公主長(zhǎng)得像剃盾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淤袜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題痒谴。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講铡羡,...
    宇宙只有巴掌大閱讀 2,363評(píng)論 0 12
  • 被文同時(shí)發(fā)布在CSDN上积蔚,歡迎查看。 APP內(nèi)存的使用烦周,是評(píng)價(jià)一款應(yīng)用性能高低的一個(gè)重要指標(biāo)尽爆。雖然現(xiàn)在智能手機(jī)的內(nèi)...
    大圣代閱讀 4,823評(píng)論 2 54
  • 我愛你漱贱,就像潺潺的山泉沁人心脾而且綿綿不絕槐雾,它雖是高山流水,卻寧可深水無(wú)聲幅狮。 我愛你募强,就像呼嘯的寒風(fēng)刺骨凌冽而且銳...
    東野顧城閱讀 741評(píng)論 0 2
  • 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的貪吃蛇小游戲感興趣的可以看看敲一下,沒(méi)事的時(shí)候還可以玩兩把
    miner敏兒閱讀 460評(píng)論 0 0
  • 第一天的作業(yè):之前的我完全沒(méi)有理財(cái)?shù)母拍睿刻炀褪浅磐砦迳习嘞掳喑缟悖總€(gè)月發(fā)的工資就在我的銀行卡里擎值,用了就取錢不用...
    地里的土豆閱讀 360評(píng)論 0 2