leakCanary 分析

一贪壳、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)存泄漏蝇更。
2c2aff757b430b65601240a19d326e5.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塘偎,一起剝皮案震驚了整個(gè)濱河市疗涉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吟秩,老刑警劉巖咱扣,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涵防,居然都是意外死亡闹伪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門壮池,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偏瓤,“玉大人,你說我怎么就攤上這事椰憋√耍” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵熏矿,是天一觀的道長已骇。 經(jīng)常有香客問我,道長票编,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任卵渴,我火速辦了婚禮慧域,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浪读。我一直安慰自己昔榴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布碘橘。 她就那樣靜靜地躺著互订,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痘拆。 梳的紋絲不亂的頭發(fā)上仰禽,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼吐葵。 笑死规揪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的温峭。 我是一名探鬼主播猛铅,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凤藏!你這毒婦竟也來了奸忽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤揖庄,失蹤者是張志新(化名)和其女友劉穎栗菜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抠艾,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苛萎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了检号。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腌歉。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖齐苛,靈堂內(nèi)的尸體忽然破棺而出翘盖,到底是詐尸還是另有隱情,我是刑警寧澤凹蜂,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布馍驯,位于F島的核電站,受9級特大地震影響玛痊,放射性物質(zhì)發(fā)生泄漏汰瘫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一擂煞、第九天 我趴在偏房一處隱蔽的房頂上張望混弥。 院中可真熱鬧,春花似錦对省、人聲如沸蝗拿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哀托。三九已至,卻和暖如春劳秋,著一層夾襖步出監(jiān)牢的瞬間仓手,已是汗流浹背胖齐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俗或,地道東北人市怎。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像辛慰,于是被迫代替她去往敵國和親区匠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

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