內存泄漏分析框架LeakCanary的使用與原理解析

 在《Android性能優化(1):常見內存泄漏與優化(一)》和《Android性能優化(1):常見內存泄漏與優化(二)》文章中,我們詳細剖析了垃圾回收器機制、內存泄漏的產生以及分析內存泄漏的各種工具,本文將此基礎上,首先回顧一下什麼是內存泄漏、Android常見的內存泄漏有哪些以及該如何優化,然後重點介紹內存泄漏分析工具LeakCanary的基本使用和實現原理。

1. 常見內存泄漏

 內存泄漏的表述可爲:當一個對象已經不需要再使用本該被回收時,另外一個正在使用的對象持有它的引用從而導致它不能被垃圾收集器回收,結果它們就一直存在於內存中(通常指Java堆內存),佔用有效空間,永遠無法被刪除。隨着內存不斷泄漏,堆中的可用空間就不斷變小,這意味着爲了執行常用的程序,垃圾清理需要啓動的次數越來越多,非常嚴重的話會直接造成應用程序報OOM異常。通常,使用可達性分析算法判斷對象是否死亡:

1.1 “單例模式” 造成的內存泄漏

 由於單例模式的靜態特性(instance對象被static關鍵詞修飾),Commontils對象instance的生命週期將於應用進程的一致。假如我們向CommonUtils的構造方法中傳入一個Activity,後面如果這個Activity對象已經不再需要了,而Commontils對象該持有該對象的引用就會使得GC無法對其進行正常回收,從而導致了內存泄漏。優化:對於需要傳入Context參數的情況,儘量使用Application的Context,因爲它會伴隨着應用進程的存在而存在。

/** 單例模式內存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:23
 * @Description:
 */
public class CommonUtils {
    // static修飾,生命週期與Application一致
    private static CommonUtils instance;
    private Context mCtx;
	
    private CommonUtils(Context context){
        this.mCtx = context;
    }

    public static CommonUtils getInstance(Context context) {
        if(instance == null) {
            instance = new CommonUtils(context);
        }
        return instance;
    }
}

1.2 “靜態實例” 造成內存泄漏

 有時爲了防止一個對象的反覆重建,就會使用static修飾關鍵字將這個對象聲明爲靜態實例,以確保該實例始終存在的是同一個,且它的生命週期與應用相同,假設這個實例爲SomeResources。然而,由於SomeResources類是一個非靜態內部類,它默認持有外部類StaticInstanceActivity的引用,就會導SomeResources的對象一直持有該引用,造成內存泄漏。優化:使用單例模式實現SomeResources,或者將其改成靜態內部類。如果需要傳入Context參數,必須使用Application的Context。

/**非靜態內部類創建靜態實例造成的內存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:43
 * @Description:
 */
public class StaticInstanceLeakActivity extends AppCompatActivity {
    // 靜態屬性,生命週期與Application一致
    private static SomeResources mSomeResources;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        if(mSomeResources == null) {
            mSomeResources = new SomeResources(this);
        }
    }

    class SomeResources {
        private Context mCtx;
        
        public SomeResources(Context context) {
            this.mCtx = context;
        }
    }
}

1.3 “Handler” 造成的內存泄漏

 由於Handler匿名內部類默認持有外部類HandlerActivity的引用,假如HandlerActivity已經不再被使用了,但是由於MessageQueue仍然有消息要處理,那麼就會導致HandlerActivity對象被Handler一直持有,從而導致HandlerActivity對象無法被GC正常回收,進而造成內存泄漏。優化:將Handler類獨立出來,或者使用靜態內部類,因爲靜態內部類不持有外部類的引用。如果使用持有外部類HandlerActivity對象,可以使用弱引用實現。

/** 使用Handler造成內存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:55
 * @Description:
 */
public class HandlerLeakActivity extends AppCompatActivity {
	
    // 匿名內部類
    // 默認持有外部類HandlerActivity對象的引用
    private Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 處理耗時任務
                // ...
                
                mUIHandler.sendEmptyMessage(0x00);
            }
        });
    }
}

1.4 “線程” 造成的內存泄漏

 由於Java類中的非靜態內部類和匿名內部類默認持有外部類的引用。對於下述示例中的MyRunnable來說,它是一個非靜態內部類,將默認持有ThreadActivity對象的引用。假如子線程的任務在ThreadActivity銷燬之前還未完成,就會導致ThreadActivity無法被GC正常回收,造成內存泄漏。優化:將MyRunnable獨立出來或使用靜態內部類(static關鍵字修飾類),因爲靜態內部類不持有外部類的引用。

/** 使用線程造成的內存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:04
 * @Description:
 */
public class ThreadLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 開啓一個子線程
        new Thread(new MyRunnable()).start();
    }

    class MyRunnable implements Runnable {

        @Override
        public void run() {
			// 執行耗時任務
            Thread.sleep(100000);
        }
    }
}

1.5 “使用WebView” 造成的內存泄漏

 使用WebView造成泄漏的原因是在不使用WebView時沒有調用其destory方法來銷燬它,導致其長期佔用內存且不能被回收。在優化時,可以爲WebView開啓另外一個進程,通過AIDL與主線程進行通信,便於WebVIew所在的進程可以根據業務需要選擇合適的時機進行銷燬。

/** 使用WebView造成的內存泄漏案例與優化
 * @Auther: Jiangdg
 * @Date: 2019/12/26 10:04
 * @Description:
 */
public class WebViewLeakActivity extends AppCompatActivity {
    private WebView mWebView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mWebView = (WebView)findViewById(R.id.webview);
        mWebView.loadUrl("https://blog.csdn.net/andrexpert");
    }

     @Override
    protected void onDestroy() {
        super.onDestroy();
        // 優化
        destoryWebView();
        Process.killProcess(Process.myPid());
    }
    
    private void destoryWebView() {
        if(mWebView != null) {
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destory();
        }
    }
}

2. LeakCanary使用與原理解析

LeakCanarysquare開源的一個檢測內存泄漏的工具,它使用非常簡單,主要用來檢測Activity和Fragment內存泄漏,如果發生內存泄漏,直接在用UI顯示哪裏發生了泄漏並展示對象引用鏈。LeakCanary效果如下:
在這裏插入圖片描述

2.1 LeakCanary使用方法

(1) 添加app.gradle依賴 :

  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

(2)初始化LeakCanary,在Application中添加:

public class ExampleApplication extends Application {

    @Override public void onCreate() {
        super.onCreate();
        // 判斷當前進程是否爲LeakCanary進程,該進程運行一個HeapAnalyzerService服務
        // 如果不是,則初始化LeakCanary進程
        if (! LeakCanary.isInAnalyzerProcess(this)) {
            LeakCanary.install(this);
        }
        
        // Normal app init code...
    }
}

 注意:除了activities和fragments外,LeakCanary支持監聽應用中的任何對象,假如這個對象不再使用到的話,通過執行下列代碼實現對某個對象的監聽。代碼如下:

RefWatcher.watch(myDetachedView)

 LeakCanary使用非常簡單,完成上述兩部的配置後,當我們在debug模式下構建、運行app時,LeakCanary就能檢測出Activity或Fragment是否出現了內存泄漏,並以通知的形式給出監測結果。另外,需要注意的是,目前LeakCanary最新版爲2.0,這個版本是純Kotlin開發,考慮到大衆性,這裏選取1.6.3版本,也就是最後一次用Java開發的版本,我們接下來的源碼分析也是基於此版本進行的。

2.2 LeakCanary原理解析

 LeakCanary的入口方法是LeanCanary$install方法,該方法源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application)   // 創建一個AndroidRefWatcherBuilder對象
               .listenerServiceClass(DisplayLeakService.class) // 註釋1
               .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 註釋2
               .buildAndInstall(); // 註釋2
}

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
}

 install方法目的就是創建並返回一個RefWatcher對象,這個RefWatcher是LeakCanary的核心類,通過建造者模式構建。其中,listenerServiceClass方法傳入了展示分析結果的Service(DisplayLeakService);excludedRefs方法排除開發中可以忽略的泄漏路徑;buildAndInstall是主要函數,實現對activity的釋放監聽。接下來,我們直接看buildAndInstall方法源碼:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// AndroidRefWatcherBuilder.java
public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should 
                                                + "only be called once.");
    }
    // 構建一個RefWacher對象
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
        if (enableDisplayLeakActivity) {
            LeakCanaryInternals.setEnabledAsync(context, 
                                                DisplayLeakActivity.class, true);
        }
        // 監聽所有Activities
        if (watchActivities) {
            ActivityRefWatcher.install(context, refWatcher);
        }
        // 監聽所有fragments
        if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
        }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
}

 從上述源碼可知,它首先會調用AndroidRefWatcherBuilder的build方法構建一個RefWatcher實例,然後分別調用ActivityRefWatcher.install方法和FragmentRefWatcher.Helper.install方法實現對所有activities和fragments的釋放監聽。下面我們就以分析如何監聽activity爲例:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    // 獲取應用的application
    Application application = (Application) context.getApplicationContext();
    // 實例化一個ActivityRefWatcher對象
    ActivityRefWatcher activityRefWatcher = 
        				new ActivityRefWatcher(application, refWatcher);
    // 調用registerActivityLifecycleCallbacks來監聽Activity的生命週期
    application.registerActivityLifecycleCallbacks(activityRefWatcher.
                                                 lifecycleCallbacks);
}

 從install方法源碼可以看出,LeakCanary主要通過調用Application的registerActivityLifecycleCallbacks方法實現對activity釋放(銷燬)監聽,該方法主要用來統一管理所有activity的生命週期。所有Activity在銷燬時都會回調ActivityLifecycleCallbacks的onActivityDestroyed方法,也就是說,LeakCanary是在Activity的onDestory方法中實施監聽的,通過調用RefWatcher.watch方法實現。源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
    @Override public void onActivityDestroyed(Activity activity) {
        // 在Activity銷燬時,監控當前activity
        // 傳入的是activity的引用
        refWatcher.watch(activity);
    }
};

 接下來,我們來看LeakCanary是如何監聽activity是否發生泄漏。RefWatcher.watch源碼:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
public void watch(Object watchedReference) {
    // watchedReference爲被監視的activity引用
    watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
        return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    // 自動生成一個主鍵,作爲全局唯一標識符
    // 並插入到retainedKeys集合中
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    // 1. 將activity的引用包裝到KeyedWeakReference中
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 2. 檢測是否發生泄漏
    ensureGoneAsync(watchStartNanoTime, reference);
}

 在RefWatcher.watch方法中,完成以下兩件事情:

首先,將當前被監控的activity引用、自動生成的key和一個ReferenceQueue 對象包裝到一個KeyedWeakReference對象中,該對象繼承於WeakReference(弱引用)。監測機制利用了Java的WeakReference和ReferenceQueue,通過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity對象如果能夠被回收,則說明引用可達,垃圾回收器就會將該WeakReference引用(包含被監控的activity)放到ReferenceQueue中,通過監測ReferenceQueue裏面的內容就能檢查到Activity是否能夠被回收。KeyedWeakReference類源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \KeyedWeakReference.java

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
private final ReferenceQueue<Object> queue;
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");
        this.name = checkNotNull(name, "name");
    }
}

其次,檢測當前被監控的activity是否發生了泄漏,通過調用RefWatcher#ensureGoneAsync方法實現,該方法又調用了RefWatcher#ensureGone。相關源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
        @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
        }
    });
}

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 1. 確定是否存在內存泄漏
    // (1)判斷當前activity的引用是否存在ReferenceQueue中,
    //  如果存在,則說明引用可達,能夠被GC回收,同時將其key從retainedKeys集合中刪除
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
    }
    // (2)確定retainedKeys集合中是否存在該activity對應的key
    // 如果不存在了,說明該對象已經被回收,直接返回
    if (gone(reference)) {
        return DONE;
    }
    // (3)如果存在,先觸發一下GC操作,再嘗試判斷該activity的對象引用
    // 是否保存到了ReferenceQueue
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    
    // 2. 再次確定retainedKeys集合中是否存在該activity對應的key
    // 如果仍然存在,則說明發生了內存泄漏.生成堆內存快照,分析快照
    if (!gone(reference)) {
        long startDumpHeap = System.nanoTime();
        long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
        // (1) 創建heapDump文件,還沒寫入
        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        }
        long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() 
                                                       - startDumpHeap);
		//(2)創建HeapDump對象
        HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
            .referenceKey(reference.key)
            .referenceName(reference.name)
            .watchDurationMs(watchDurationMs)
            .gcDurationMs(gcDurationMs)
            .heapDumpDurationMs(heapDumpDurationMs)
            .build();
        //(3)調用heapdumpListener分析
        // 調用HeapAnalyzerService的analyze實現,即後臺執行分析任務
        heapdumpListener.analyze(heapDump);
    }
    return DONE;
}

 從ensureGone方法源碼可知,它首先會去確定被監控的activity對象是否發生了內存泄漏,然後如果確定了確實發生了泄漏,就會dump內存快照並對快照進行分析,最終得到泄漏的具體信息。接下來,我們將對這三個方面進行詳細分析:

(1)確定是否存在內存泄漏

 確定被監控的activity對象是否存在內存泄漏,主要是通過調用removeWeaklyReachableReferencesgone方法實現的,具體策略爲:首先,調用removeWeaklyReachableReferences判斷當前被監控的activity對象的引用是否存在ReferenceQueue中,如果存在說明,該activity對象引用可達,能夠被GC回收,此時就將其key從retainedKeys這個Set集合中移除;然後,調用gone方法確定當前被監控的activity對象的引用是否存在retainedKeys集合,如果不存在,說明該activity對象已經被回收,直接返回。但是,如果仍然存在,爲了確定GC延遲或誤判,手動觸發一下GC操作,然後再進行一次上面的判定操作,如果Gone方法仍然返回false,則說明被監控的activity對象發生了內存泄漏。相關源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private boolean gone(KeyedWeakReference reference) {
    // 判定被監控的activity對象對應的key
    // 是否存在於retainedKeys集合中
    // 如果不存在,說明該對象已經被GC回收,不存在內存泄漏
    return !retainedKeys.contains(reference.key);
}

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
	// 判定被監控的activity引用是否保存在ReferenceQueue中
    // 如果存在,則將其對應的key從retainedKeys集合中移除
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}

(2)將堆內存轉儲到文件並分析,獲取泄漏對象的GC最短強引用路徑

 前面說到,如果Gone方法返回false說明被監控的activity對象發生了內存泄漏,接下來,將進入內存泄漏分析過程。具體策略爲:首先,創建一個heapDump文件,此時還沒有寫入;然後,將堆內存信息轉儲到該heapDump文件,並創建一個HeapDump對象;最後,調用heapdumpListener的analyze方法進入分析流程。需要注意的是,heapdumpListener由RefWatch構造方法傳入,前面說到RefWatch對象是通過建造者模式的形式創建的,因此,我們找到了AndroidRefWatcherBuilder,該類包含一個defaultHeapDumpListener方法即可說明heapdumpListener(類型爲HeapDump.Listener)的實例化過程,即實現類爲ServiceHeapDumpListener。也就是說,heapdumpListener.analyze爲調用ServiceHeapDumpListener#analyze方法,該方法中最終調用的是HeapAnalyzerService#runAnalysis方法在後臺服務中執行堆快照分析任務。相關源碼如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ServiceHeapDumpListener.java
public final class ServiceHeapDumpListener implements HeapDump.Listener {

    private final Context context;
    private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

    public ServiceHeapDumpListener(@NonNull final Context context,
                                   @NonNull final Class<? extends 
                        AbstractAnalysisResultService> listenerServiceClass) {
        this.listenerServiceClass = checkNotNull(listenerServiceClass, 
                                                 "listenerServiceClass");
        this.context = checkNotNull(context, "context").getApplicationContext();
    }

    @Override public void analyze(@NonNull HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        // 後臺執行分析任務
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
    }
}

 接下來,我們分析HeapAnalyzerService#runAnalysis方法,它的源碼如下:

public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {
    ...
    public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    	setEnabledBlocking(context, HeapAnalyzerService.class, true);
    	setEnabledBlocking(context, listenerServiceClass, true);
        // listenerServiceClass
	    // 負責記錄日誌和展示通知
    	Intent intent = new Intent(context, HeapAnalyzerService.class);
    	intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    	intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    	// 啓動自身,並將其置爲前臺
    	ContextCompat.startForegroundService(context, intent);
  }
}

 從HeapAnalyzerService源碼可知,它是一個IntentServcie,實際上就是一個Service,只是與普通Service不同的是,它被啓動後會在子線程執行具體的任務,即調用onHandleIntentInForeground方法執行任務,當任務執行完畢後,該Service會被自動銷燬。在HeapAnalyzerService#runAnalysis方法中,就是啓動該IntentService,並將其置爲前臺服務,以降低被系統殺死的概率。現在,我們就看下HeapAnalyzerService#onHandleIntentInForeground方法做了什麼,源碼如下:

protected void onHandleIntentInForeground(@Nullable Intent intent) {
    // DisplayLeakService.class
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); 
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    // 創建HeapAnalyzer
    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, 
                         heapDump.reachabilityInspectorClasses);
    // HeapAnanlyzer工具分析
    // 即分析堆內存快照,找出 GC roots 的最短強引用路徑,並確定是否是泄露
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, 
                                                      heapDump.referenceKey,
                                                   heapDump.computeRetainedHeapSize);
    // 啓動DisplayLeakService記錄日誌和展示通知
    AbstractAnalysisResultService.sendResultToListener(this, 
                                                       listenerClassName, 
                                                       heapDump, result);
}

 從該方法源碼可知,它主要是創建一個HeapAnalyzer對象,並調用該對象的checkForLeak進行分析,然後將得到的結果交給DisplayLeakService進行通知展示。這裏,我們分析下HeapAnalyzer#checkForLeak方法分析堆內存快照文件的流程,該方法源碼如下:

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();
	// 確定堆快照文件是否存在
    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " 
                                                         + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
	  // 將heap文件封裝成MemoryMappedFileBuffer
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
	  // 創建hprof解析器,解析hprof文件
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
	  // 移除相同GC root
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
	  // 找出泄漏的對象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      //檢測是否存在泄漏的引用
      if (leakingRef == null) {
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
	   //根據leakingRef尋找引用路徑
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, 
                           computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

 由上述源碼可知,該方法最終調用findLeakingReference方法來判斷是否真的存在內存泄漏,如果存在(leakingRef!=null),就調用findLeakTrace方法找出這個泄漏對象的GC Root最短強引用路徑。由於這部分不是我關注的重點,就不繼續分析下去了,感興趣的朋友可以繼續往下面分析。LeakCanary時序圖如下:
在這裏插入圖片描述
 至此,LeakCanary框架實現原理分析完畢,最後我們再總結一下:LeakCanary是通過在Application的registerActivityLifecycleCallbacks方法實現對Activity銷燬監聽的,該方法主要用來統一管理所有activity的生命週期。所有Activity在銷燬時在其OnDestory方法中都會回調ActivityLifecycleCallbacks#onActivityDestroyed方法,而LeakCanary要做的就是在該方法中調用RefWatcher#watch方法實現對activity進行內存泄漏監控。那麼,LeakCanary是如何判斷某個Activity可能會發生內存泄漏呢?答案是:WeakReference和ReferenceQueue,即LeakCanary利用了Java的WeakReference和ReferenceQueue,通過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity對象如果能夠被回收,則說明引用可達,垃圾回收器就會將該WeakReference引用存放到ReferenceQueue中。假如我們要監視某個activity對象,LeakCanary就會去ReferenceQueue找這個對象的引用,如果找到了,說明該對象是引用可達的,能被GC回收,如果沒有找到,說明該對象有可能發生了內存泄漏。最後,LeakCanary會將Java堆轉儲到一個.hprof文件中,再使用Shark(堆分析工具)分析.hprof文件並定位堆轉儲中“滯留”的對象,並對每個"滯留"的對象找出 GC roots 的最短強引用路徑,並確定是否是泄露,如果泄漏,建立導致泄露的引用鏈。最後,再將分析完畢的結果以通知的形式展現出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章