Leak Canary原理分析

在分析Leak Canary原理以前,咱們先來簡單瞭解WeakReference和ReferenceQueue的做用,爲何要了解這些知識呢?Leak Canary其實內部就是使用這個機制來監控對象是否被回收了,固然Leak Canary的監控僅僅針對Activity和Fragment,因此這塊有引入了ActivityLifecycleCallBack,後面會說,這裏的回收是指JVM在合適的時間觸發GC,並將回收的WeakReference對象放入與之關聯的ReferenceQueue中表示GC回收了該對象,Leak Canary經過上賣弄的檢測返現有些對象的生命週期本該已經結束了,可是任然在佔用內存,這時候就斷定是已經泄露了,那麼下一步就是開始解析析headump文件,分析引用鏈,至此就結束了,其中須要注意的是這是WeakReference.get方法獲取到的對象是null,因此Leak Canary使用了繼承WeakReference.類,並把傳入的對象做爲成員變量保存起來,這樣當GC發生時雖然把WeakReference中引用的對象置爲了null也不會把WeakReference中咱們拓展的類的成員變量置爲null,這樣咱們就能夠作其餘的操做,好比:Leak Canary中把WeakReference存放在Set集合中,在恰當的時候須要移除Set中的WeakReference的引用,這個機制Glide中的內存緩存 也是使用了該機制,關於WeakReference和ReferenceQueue機制就很少說網上有不少能夠了解一下。java

WeakReference和ReferenceQueue機制

/**
 * 監控對象被回收,由於若是被回收就會就如與之關聯的隊列中
 */
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();
複製代碼

輸出的日誌:android

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
複製代碼

上面是一個監控對象回收,由於若是對象被回收就把該對象加入如與之關聯的隊列中,接着開啓線程製造觸發GC,並開啓線程監控對象回收,Leak Canary也是利用這個機制完成對一些對象本該生命週期已經結束,還常駐內存,就算觸發GC也不會回收,Leak Canary就判斷爲泄漏,針對於內存泄漏,咱們知道有些對象是不能被GC回收的,JVM虛擬機的回收就是可達性算法,就是從GC Root開始檢測,若是不可達那麼就會被第一次標誌,再次GC就會被回收。git

可以做爲 GC Root的對象

  • 虛擬機棧,在你們眼裏也叫做棧(棧幀中的本地變量表)中引用的對象;
  • 方法區中類靜態屬性引用的對象;
  • 方法區中常量引用的對象;
  • 本地方法棧中JNI引用的對象;

Leak Canary是如何判斷Activity或Fragment的生命週期結束了呢?

  • Leak Canary是經過 Application的內部類ActivityLifecycleCallbacks檢測Activity的生命週期是否結束了,若是回調了onActivityDestroyed方法,那麼表示Activity的聲明週期已經結束了,這時候就要執行GC檢測了。
  • 對於Fragment是經過FragmentManager的內部接口FragmentLifecycleCallbacks檢測Fragment的聲明週期的相似ActivityLifecycleCallbacks接口。

開始Leak Canary源碼解讀

步驟無非就是: 一、安裝,實際上就是作一些初始化的操做; 二、檢測時機,好比:回調onActivityDestroyed方法開始檢測; 三、UI的展現;github

安裝

Leak Canary的地方就是 LeakCanary.install(this)方法開始,代碼以下:算法

通常咱們使用Leak Canaryu都是在Application中調用:緩存

public class ExampleApplication extends Application {
	@Override
    public void onCreate() {
	  super.onCreate();
	  setupLeakCanary();
	}

   protected void setupLeakCanary() {
     enabledStrictMode();
     if (LeakCanary.isInAnalyzerProcess(this)) {
         return;
      }
   LeakCanary.install(this);
  }
   ...
 }
複製代碼

在install方法以前有個判斷,這個判斷是用來判斷是不是在LeakCanary的堆統計進程(HeapAnalyzerService),也就是咱們不能在咱們的App進程中初始化LeakCanary,代碼以下:app

/**
 * 當前進程是不是運行{@link HeapAnalyzerService}的進程中,這是一個與普通應用程序進程不一樣的進程。
 */
public static boolean isInAnalyzerProcess(@NonNull Context context) {
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // 這裏只須要爲每一個進程計算一次。
    if (isInAnalyzerProcess == null) {
        //把Context和HeapAnalyzerService服務做爲參數傳進isInServiceProcess方法中
        isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
        LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
}
複製代碼

在isInAnalyzerProcess方法中有調用了isInServiceProcess方法,代碼以下:dom

public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
    PackageManager packageManager = context.getPackageManager();
    PackageInfo packageInfo;
    try {
        packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
    } catch (Exception e) {
        CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
        return false;
    }
    //主進程
    String mainProcess = packageInfo.applicationInfo.processName;


    //構造進程
    ComponentName component = new ComponentName(context, serviceClass);
    ServiceInfo serviceInfo;
    try {
        serviceInfo = packageManager.getServiceInfo(component, PackageManager.GET_DISABLED_COMPONENTS);
    } catch (PackageManager.NameNotFoundException ignored) {
        // Service is disabled.
        return false;
    }

    //判斷當前HeapAnalyzerService服務進程名和主進程名是否相等,若是相等直接返回false,由於LeakCanary不能再當前進程中運行
    if (serviceInfo.processName.equals(mainProcess)) {
        CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
        // Technically we are in the service process, but we're not in the service dedicated process.
        return false;
    }

    int myPid = android.os.Process.myPid();
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.RunningAppProcessInfo myProcess = null;
    List<ActivityManager.RunningAppProcessInfo> runningProcesses;
    try {
        runningProcesses = activityManager.getRunningAppProcesses();
    } catch (SecurityException exception) {
        // https://github.com/square/leakcanary/issues/948
        CanaryLog.d("Could not get running app processes %d", exception);
        return false;
    }
    if (runningProcesses != null) {
        for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
            if (process.pid == myPid) {
                myProcess = process;
                break;
            }
        }
    }
    if (myProcess == null) {
        CanaryLog.d("Could not find running process for %d", myPid);
        return false;
    }

    return myProcess.processName.equals(serviceInfo.processName);
}
複製代碼

實際上LeakCanary最終會調用LeakCanaryInternals.isInServiceProcess方法,經過PackageManager、ActivityManager以及android.os.Process來判斷當前進程是否爲HeapAnalyzerService運行的進程,由於咱們不能在咱們的App進程中初始化LeakCanary。ide

接下來咱們開始LeakCanary真正的實現,從LeakCanary.install(this)方法開始,代碼以下:ui

public static RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
}
複製代碼

實際上這一步返回的RefWatcher的實現類AndroidRefWatcher,主要是作些關乎初始化的操做,這些不展開講,直接進入buildAndInstall()方法中,代碼以下:

public RefWatcher buildAndInstall() {

    //install()方法只能一次調用,屢次調用將拋出異常
    if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }

    //初始化RefWatcher,這個東西是用來檢查內存泄漏的,包括解析堆轉儲文件這些東西
    RefWatcher refWatcher = build();

    //若是RefWatcher尚未初始化,就會進入這個分支
    if (refWatcher != DISABLED) {
        if (enableDisplayLeakActivity) {
            //setEnabledAsync最終調用了packageManager.setComponentEnabledSetting,
            // 將Activity組件設置爲可用,即在manifest中enable屬性。
            // 也就是說,當咱們運行LeakCanary.install(this)的時候,LeakCanary的icon才顯示出來
            LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
        }


        //ActivityRefWatcher.install和FragmentRefWatcher.Helper.install的功能差很少,註冊了生命週期監聽。
        // 不一樣的是,前者用application監聽Activity的生命週期,後者用Activity監聽也就是Activity回調onActivityCreated方法,
        // 而後獲取FragmentManager調用registerFragmentLifecycleCallbacks方法註冊,監聽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方法中有幾點:

  • 首先會調用RefWatcherBuilder.build方法建立RefWatcher,RefWatcher是檢測內存泄漏相關的;
  • 緊接着將Activity組件設置爲可用,即在manifest中enable屬性,也就是說,當咱們運行LeakCanary.install(this)的時候,LeakCanary的icon纔在桌面纔會顯示出來;
  • 而後就是ActivityRefWatcher.install和FragmentRefWatcher.Helper.install方法,註冊了Activity和Fragment的生命週期監聽,不一樣的是,前者用application監聽Activity的生命週期,後者用Activity監聽也就是Activity回調onActivityCreated方法,而後經過Activity獲取FragmentManager調用並FragmentManager的registerFragmentLifecycleCallbacks方法註冊監聽fragment的生命週期,並且用到了leakcanary-support-fragment包,兼容了v4的fragment。

RefWatcher類是用來監控對象的引用是否可達,當引用變成不可達,那麼就會觸發堆轉儲(HeapDumper),來看看RefWatcherBuilder.build方法的具體實現,代碼以下:

public final RefWatcher build() {

    //若是已經初始化了,直接返回RefWatcher.DISABLED表示已經初始化了
    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();
    }


    //建立堆轉儲對象
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
        //返回的是HeapDumper.NONE,HeapDumper內部實現類,
        heapDumper = defaultHeapDumper();
    }

    //建立監控線程池
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
        //默認返回 NONE
        watchExecutor = defaultWatchExecutor();
    }


    //默認的Gc觸發器
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
        gcTrigger = defaultGcTrigger();
    }

    if (heapDumpBuilder.reachabilityInspectorClasses == null) {
        heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
    }
    
    //建立把參數構造RefWatcher
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
            heapDumpBuilder);
}
複製代碼

如上代碼知道,其實是爲了建立RefWatcher實例,和一些在檢測中的環境初始化,好比線程池、GC觸發器等等。

回到最初的biuldInstall方法中,知道監控Activity和Fragment是查不到的因此這裏就只分析Activity相關的,也就是ActivityRefWatcher.install方法,代碼以下:

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    //註冊ActivityLifecycleCallbacks監聽每個Activity的生命週期
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
複製代碼

能夠知道這裏是使用的裝飾模式,使用ActivityRefWatcher對RefWatcher作了包裝,接着註冊ActivityLifecycleCallbacks監聽每個Activity的生命週期的onActivityDestroyed方法,這也就是檢測泄漏開始的地方,而在onActivityDestroyed方法方法中會調用refWatcher.watch方法把activity做爲參數傳進去,代碼以下:

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
    @Override
    public void onActivityDestroyed(Activity activity) {
        //當Activity被銷燬了,那麼應該檢測是否內存泄漏
        refWatcher.watch(activity);
    }
};
複製代碼

能夠看到在Activity銷燬時會回調onActivityDestroyed方法,而後把該activity做爲參數傳遞給refWatcher.watch(activity)方法,watch方法代碼以下:

public void watch(Object watchedReference, String referenceName) {
   
	..........

    final long watchStartNanoTime = System.nanoTime();
    //給該引用生成UUID
    String key = UUID.randomUUID().toString();
    //給該引用的UUID保存至Set中,強引用
    retainedKeys.add(key);
    //KeyedWeakReference 繼承至WeakReference,因爲KeyedWeakReference若是回收了,那麼當中的對象經過get返回的是null,
    // 因此須要保存key和name做爲標識,Glide也是此作法,KeyedWeakReference實現WeakReference
    final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);

    //開始檢測
    ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼

在watch方法中有以下幾點:

  • 首先經過UUID生成表示該引用的Key,而這個Key會當作強引用保存到RefWatcher的成員變量Set集合中;
  • 接着建立KeyedWeakReference,而KeyedWeakReference 繼承至WeakReference,因爲KeyedWeakReference若是回收了,那麼當中的對象經過get返回的是null,因此爲了能在GC以後拿到Key,須要將保存key和name做爲KeyedWeakReference中,Glide也是此作法;
  • 接着調用ensureGoneAsync(watchStartNanoTime, reference)方法開始檢測是否有內存泄漏;

ensureGoneAsync方法代碼以下:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
        @Override
        public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
        }
    });
}
複製代碼

在ensureGoneAsync方法中直接執行線程池(AndroidWatchExecutor),而這個線程池就是在剛開始的時候LeakCanary.install方法中建立RefWatcher的子類AndroidRefWatcher的時候建立的,接着看看ensureGone方法,代碼以下:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    //gc 開始的時間
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    //從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
    }
    if (gone(reference)) {
        return DONE;
    }

    //嘗試GC
    gcTrigger.runGc();

    //從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險
    removeWeaklyReachableReferences();


    //到這一步說明該對象按理來講聲明週期是已經結束了的,可是經過前面的GC卻不能回收,說明已經形成了內存泄漏,那麼解析hprof文件,獲得該對象的引用鏈,也就是要觸發堆轉儲
    if (!gone(reference)) {
        long startDumpHeap = System.nanoTime();
        long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        }
        long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

        HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
                .referenceName(reference.name)
                .watchDurationMs(watchDurationMs)
                .gcDurationMs(gcDurationMs)
                .heapDumpDurationMs(heapDumpDurationMs)
                .build();
        //開始解釋堆轉儲文件
        heapdumpListener.analyze(heapDump);
    }
    return DONE;
}
複製代碼

在ensureGone方法中有以下幾點:

  • 調用removeWeaklyReachableReferences方法,從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險,就將引用從Set集合中移除;
  • 緊接着調用gcTrigger.runGc方法嘗試GC,看看能不能回收引用對象;
  • 再次調用removeWeaklyReachableReferences方法,從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險,也就是在手動觸發GC以後,再次檢測是否能夠回收對象; *最後經過gone(reference)方法檢測Set集合中是否還存在該對象,若是存在說明已經泄漏了,就像前面說的,若是發生GC而且對象是能夠被回收的,那麼就會加入引用隊列, 最後到這一步說明該對象按理來講聲明週期是已經結束了的,可是經過前面的GC卻不能回收,說明已經形成了內存泄漏,那麼解析hprof文件,獲得該對象的引用鏈,也就是要觸發堆轉儲。

在前面說不少檢測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會在它們指向的對象變得沒法訪問時排隊。 這是在完成或垃圾收集實際發生以前。
    //隊列不爲null,說明該對象被收了,加入此隊列
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}
複製代碼

這裏直接使用一個while循環從隊列取出元素進行判斷,這裏的queue.poll()是不會阻塞的,因此爲何LeakCanary會作兩次驗證的緣由,爲何LeakCanary不使用queue.remove()方法呢?你想一想queue.remove()方法是阻塞當前線程,從前面知道每次Activity或者Fragment銷燬回調生命週期方法都會建立一個KeyedWeakReference實例,也就是說若是不泄露就一直阻塞當前線程,這樣反而對形成沒必要要的開銷,我也是猜猜而已。

總結

  • LeakCanary是經過WeakReference+Reference機制檢測對象是否能被回收;
  • LeakCanary檢測的時機是當某組件的生命週期已經結束,纔會觸發檢測;
相關文章
相關標籤/搜索