LeakCanary原理分析整理

Reference概述

Reference

Reference主要負責內存的一個狀態,Reference類把內存分爲四種狀態:

  • Active:內存一開始分配的都是Active
  • Pending:快要放進隊列的對象,馬上回收的對象。
  • Enqueued:對象已經被回收了,已經對象放入隊列中了。
  • Inactive:最終狀態,不能再變爲其他狀態。

ReferenceQueue

引用隊列,在對象的可達性更改後,垃圾回收器將已註冊的引用對象添加到隊列中。

舉個例子

檢測一個對象是否被回收,可以使用Reference + ReferenceQueue

  • 創建一個引用隊列 queue
  • 創建 Refrence 對象,並關聯引用隊列 queue
  • 在 reference 被回收的時候,refrence 會被添加到 queue 中
創建一個引用隊列  
ReferenceQueue queue = new ReferenceQueue();  

// 創建弱引用,此時狀態爲Active,並且Reference.pending爲空,當前Reference.queue = 上面創建的queue,並且next=null  
WeakReference reference = new WeakReference(new Object(), queue);  
System.out.println(reference);  
// 當GC執行後,由於是虛引用,所以回收該object對象,並且置於pending上,此時reference的狀態爲PENDING  
System.gc();  

/* ReferenceHandler從pending中取下該元素,並且將該元素放入到queue中,此時Reference狀態爲ENQUEUED,Reference.queue = ReferenceENQUEUED */  

/* 當從queue裏面取出該元素,則變爲INACTIVE,Reference.queue = Reference.NULL */  
Reference reference1 = queue.remove();  
System.out.println(reference1);

LeakCanary原理

檢測泄漏的步驟

  • 第一步,獲得activity被銷燬的信息,也就是要監聽到activity的生命週期方法。
  • 第二步,判斷該對象(activity)是否被回收。若回收了那就是沒泄漏,若判斷該對象沒被回收,說明該對象泄漏了。
  • 進行第三步,展示泄漏信息。

LeakCanary源碼

第一步——監聽

使用上只需在Application.onCreate()中調用一句LeakCanary.install(this)即可。

進入install方法

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
}

標準的建造者模式

  • listenerServiceClass、excludedRefs 都是給Builder成員變量賦值

最後的buildAndInstall()方法

public RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //調用build方法構造
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
        LeakCanary.enableDisplayLeakActivity(context);
        if (watchActivities) {
                    //傳入build好的RefWatch,調用靜態方法進行install
                    ActivityRefWatcher.install((Application) context, refWatcher);
        }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
}

完全是build設計模式,構造出一個RefWatcher

接下來進入install((Application) context, refWatcher)

public static void install(Application application, RefWatcher refWatcher) {
     new ActivityRefWatcher(application, refWatcher).watchActivities();
}

構造ActivityRefWatcher並調用watchActivities()監控activity

那麼,我們想想作爲第三方應用來說,如何監控應用內的每一個Activity?

可以通過系統提供給我們的接口ActivityLifecycleCallbacks,該接口的回調方法與activity生命週期回調方法對應,每當有activity創建或銷燬時,可以通過回調方法通知外部

watchActivities方法

public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

lifecycleCallbacks:

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);
            }
        };
  • 這裏只監控了activityDestory,當有activity被銷燬時,交給onActivityDestoryed()處理

onActivityDestory方法:

void onActivityDestroyed(Activity activity) {
     refWatcher.watch(activity);
}

交給watcher處理。到此,完成了第一步:監聽activity銷燬信息.

接下來研究watch()方法是如何判斷activity是否泄漏的

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
        return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
}

這裏將activity、唯一生成的key、還有queue(是一個ReferenceQueue) 構造一個KeyedWeakReference。並將key值存入retainKeys中保管。該類是WeakReference的子類,如下:

final class KeyedWeakReference extends WeakReference
    public final String key;
    public final String name;
    KeyedWeakReference(Object referent, String key, String name,
        ReferenceQueue
        super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
        this.key = checkNotNull(key, "key");
        this.name = checkNotNull(name, "name");
    }
}

這裏,我們將activity包裝弱引用,並添加了referenceQueue,那麼當該activity被GC回收時,我們就可以從referenceQueue中獲取該activity的reference。

第二步——檢測泄漏(核心)

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    //將已被回收的activity對象的keyedWeakReference的key值從retainedKeys中刪除,以達到
    //過濾目的
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
    }
    //如果retainedKeys中不存在reference,說明它已經被回收,返回
    if (gone(reference)) {
        return DONE;
    }
    //手動調用GC
    gcTrigger.runGc();
    //再次過濾
    removeWeaklyReachableReferences();
    //若retainedKeys中還存在該reference(還沒有被濾掉),則判斷爲該reference泄漏,進行下一步dump內存快照
    //展示泄漏信息
    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);
        heapdumpListener.analyze(
                new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                        gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
}

總結一下

  • 1、首先將已回收的activity對象的keyedWeakReference的key值從retainedKeys中刪除
  • 2、如果retainedKeys中不存在reference的key,說明已經回收,返回
  • 3、手動執行GC,然後執行 1 再次過濾已經回收的reference
  • 4、再次進行 2 ,若retainedKeys中還存在該reference,進行下一步dump內存快照

removeWeakReachableReferences()方法

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;
    //若queue中存在該keyedWeakReference,則說明該keyedWeakReference對應的activity已被回收
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        //從retainedKeys中移除,則retainedKeys剩下的就是泄漏的
        retainedKeys.remove(ref.key);
    }
}

其邏輯就是在queue裏面的到key,然後從retainedKeys中移除。

gone()方法:

private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
}

就是一個contains判斷

第三步——泄漏分析

第三步:展示泄漏信息,也就是獲取dumpheap以及對這個dumpheap進行analyze。

調用比較簡單,而邏輯實現又是依託另外一個項目:HAHA。

 heapdumpListener.analyze(
            new HeapDump(heapDumpFile, reference.key,
            	reference.name, excludedRefs, watchDurationMs,
                gcDurationMs, heapDumpDurationMs));

這裏的heapdumpListener是第一步構建watcher時listenerServiceClass(DisplayLeakService.class)裏面創建的

最後是ServiceHeapDumpListener這個類封裝了analyze邏輯:

@Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

runAnalysis()方法中就是啓動一個服務處理heapDump

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.onHandIntent()中收到啓動服務請求,調用checkForLeak

@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);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

checkForLeak()方法:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    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);
        //將heapDumpFile轉化爲Snapshot
        Snapshot snapshot = parser.parse();
        deduplicateGcRoots(snapshot);

        //找到泄漏對象引用
        Instance leakingRef = findLeakingReference(referenceKey, snapshot);

        // False alarm, weak reference was cleared in between key check and heap dump.
        if (leakingRef == null) {
            return noLeak(since(analysisStartNanoTime));
        }

        //返回泄漏對象的最短路徑
        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
        return failure(e, since(analysisStartNanoTime));
    }
}

主要有以下幾個步驟:

  • 把.hprof轉爲Snapshot,這個Snapshot對象就包含了對象引用的所有路徑
  • 精簡gcroots,把重複的路徑刪除,重新封裝成不重複的路徑的容器
  • 找出泄漏的對象
  • 找出泄漏對象的最短路徑

其中第三步的findLeakingReference方法

private Instance findLeakingReference(String key, Snapshot snapshot) {
    //找到快照中的KeyedWeakReference類對象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    //遍歷這個類的所有實例
    for (Instance instance : refClass.getInstancesList()) {
        List<ClassInstance.FieldValue> values = classInstanceValues(instance);
        //key值和最開始定義封裝的key值相同,說明該實例是泄漏對象
        String keyCandidate = asString(fieldValue(values, "key"));
        if (keyCandidate.equals(key)) {
            return fieldValue(values, "referent");
        }
        keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
            "Could not find weak reference with key " + key + " in " + keysFound);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章