源碼學習LeakCanary內存泄漏檢測流程

1、LeakCanary的使用
public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(this);
    }
}

LeakCanary.install(this)是分析的入口

2、LeakCanary.install(Application application)
public static RefWatcher install(Application application) {
    //refWatcher返回AndroidRefWatcherBuilder對象
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) //excludedRefs 排除不希望檢測的對象
        .buildAndInstall(); //見3
}
  
public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
}

創建了AndroidRefWatcherBuilder對象,接着調用了buildAndInstall方法

3、AndroidRefWatcherBuilder.buildAndInstall()
  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();// 見4 調了父類RefWatcherBuilder的build方法
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);//見5
    }
    return refWatcher;
  }

創建出RefWatcher對象,接着ActivityRefWatcher去installl

4、RefWatcherBuilder.build()
 public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //下面是構建RefWatcher對象所需參數
    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      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 = defaultHeapDumper();
    }

    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }

    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }
    //返回新建的RefWatcher實例
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }

構建了RefWatcher對象。顧名思義,RefWatcher對象是用來觀察引用的

5、ActivityRefWatcher.install()
public static void install(Application application, RefWatcher refWatcher) {
    //創建ActivityRefWatcher用於檢測Activity
    new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //註冊activity生命週期回調
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
  new Application.ActivityLifecycleCallbacks() {
  
    ...

    @Override public void onActivityDestroyed(Activity activity) {
        //在Activity.onDestoryed回調時,運行ActivityRefWatcher.onActivityDestroyed(activity)方法
        ActivityRefWatcher.this.onActivityDestroyed(activity);//見6
    }
};

在Activity.onDestoryed回調時,運行ActivityRefWatcher.onActivityDestroyed(activity)方法

6、ActivityRefWatcher.onActivityDestroyed(activity)
void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);//見7
}

調用了refWatcher.watch()方法,refWatcher就是第5小結構建ActivityRefWatcher對象構建方法傳入的

7、RefWatcher.watch()
public void watch(Object watchedReference) {
    watch(watchedReference, "");
}

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(); //生產和被觀察關聯的key
    retainedKeys.add(key);//retainedKeys保存key
    //創建KeyedWeakReference,它是WeakReference的子類,增加了key和name屬性。
    //把檢測對象和ReferenQueue對象也傳了進去
    final KeyedWeakReference reference = 
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
    ensureGoneAsync(watchStartNanoTime, reference);//見8
}

這裏注意下,RefWatcher.watch()接收到的是Object對象,也就是說LeakCanary本質上是支持所有對象泄漏檢測的,在檢測的地方運行RefWatcher.watch()方法即可。

8、RefWatcher.ensureGoneAsync()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);//見9
      }
    });
}

watchExecutor是一個封裝了Handler、具有重試功能的執行器。傳遞給WatchExecutor.execute方法的Retryable對象,在經過5秒延遲後會執行run方法。(5秒延遲是跟ANR有關???),run方法調了ensureGone方法

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

    removeWeaklyReachableReferences();//見10 清除入隊的弱引用

    if (debuggerControl.isDebuggerAttached()) {
      // debug模式下重試.
      return RETRY;
    }
    //gone方法如果檢測到對象被回收,說明沒有泄露
    if (gone(reference)) {//見10
      return DONE;
    }
    //觸發GC
    gcTrigger.runGc();
    removeWeaklyReachableReferences();//清除入隊的弱引用
    if (!gone(reference)) {//再次判斷是否檢測對象被回收
      //仍然沒有正常回收檢測對象
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      //dump出內存信息
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //構建HeapDump去分析內存泄露的詳細信息
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));//見11
    }
    return DONE;
}

首先運行removeWeaklyReachableReferences清除入隊的弱引用,然後gone方法判斷檢測對象是否被回收。若未被回收觸發一次gc,接着再清除入隊的弱引用。如果仍未被回收就dump出hprof文件去分析。
看下removeWeaklyReachableReferences和gone方法的實現

10、RefWatcher.gone() & removeWeaklyReachableReferences()
private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    //移除入隊的Reference
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key); //retainedKeys刪除關聯的key
    }
}

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

從queue逐個移除KeyedWeakReference對象,接着移除retainedKeys中和KeyedWeakReference對象關聯的key,也就是如果檢測對象已經到達過引用隊列,被正常回收,retainedKeys是不再包含關聯的key。所以gone方法可以通過判斷retainedKeys是否包含key來確認被檢測對像是否被回收。

11、ServiceHeapDumpListener.analyze()

heapdumpListener.analyze會走到HeapAnalyzerService.onHandleIntent()方法

protected void onHandleIntent(Intent intent) {
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
        
    //創建分析類
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //見12 分析流程
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //分析後
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

重點的一步調了heapAnalyzer.checkForLeak分析

12、HeapAnalyzer.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 {
       //MemoryMappedFileBuffer封裝heapDumpFile
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //HprofParser分析hprof文件
      HprofParser parser = new HprofParser(buffer);
      //分析結果封裝在Snapshot對象中
      Snapshot snapshot = parser.parse();//見13
      //去重GcRoot
      deduplicateGcRoots(snapshot);

      //通過key找到檢測對象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);//見14

      // 沒有泄露
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }
      //找到最短泄露路徑
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); //見15
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
}

主要有幾個步驟:

  1. 把heapDumpFile封裝成MemoryMappedFileBuffer。
  2. 通過HprofParser對象去分析MemoryMappedFileBuffer。
  3. 去除Snapshot中重複的GcRoot。
  4. 通過key找到Snapshot中保存的泄露對象。
  5. 分析GcRoot到泄露對象的最短路徑。
    checkForLeak方法有兩個東西的沒搞懂,一個Snapshot的數據結構,另一個是findLeakingReference方法。先看看Snapshot的數據結構。

下面信息根據debug追蹤、註釋、類屬性和命名等方式得出的結果,未必正確,希望有大牛指點一二

13、Snapshot 數據結構
/*
 * A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
 *
 * There are three possible heaps: default, app and zygote. GC roots are always reported in the
 * default heap, and they are simply references to objects living in the zygote or the app heap.
 * During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
 */
public class Snapshot {

    private static final String JAVA_LANG_CLASS = "java.lang.Class";

    //  Special root object used in dominator computation for objects reachable via multiple roots.
    public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);

    private static final int DEFAULT_HEAP_ID = 0;

    final HprofBuffer mBuffer;

    //堆信息
    ArrayList<Heap> mHeaps = new ArrayList<Heap>();

    Heap mCurrentHeap;

    private ImmutableList<Instance> mTopSort;

    private Dominators mDominators;

    //  保存各種Reference以及它的子類的Class信息
    private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>();

    private int[] mTypeSizes;

    private long mIdSizeMask = 0x00000000ffffffffl;

    

    ....
    //省略很多代碼
}

從源碼上來看Snapshot主要有:

  • mHeaps,Heap的列表,有四種可能default、app、image和zygote。GcRoot總是保存在default heap,並且GcRoot僅僅是指向保存在app、zygote的對象。
  • mReferenceClasses ,保存各種Reference以及它的子類的Class信息,之前使用的KeyedWeakReference也保存在這裏

接着看看Heap的數據結構

public class Heap {

    private final int mId;

    private final String mName;

    //  每個item對應着內存中棧的一幀
    TLongObjectHashMap<StackFrame> mFrames = new TLongObjectHashMap<StackFrame>();

    //  StackTrace對應着內存中的棧,每個StackTrace裏面包含多個StackFrame
    TIntObjectHashMap<StackTrace> mTraces = new TIntObjectHashMap<StackTrace>();

    //  根對象,如內部字符串、jni局部變量等
    ArrayList<RootObj> mRoots = new ArrayList<RootObj>();

    //  線程列表
    TIntObjectHashMap<ThreadObj> mThreads = new TIntObjectHashMap<ThreadObj>();

    //  Class信息
    TLongObjectHashMap<ClassObj> mClassesById = new TLongObjectHashMap<ClassObj>();

    Multimap<String, ClassObj> mClassesByName = ArrayListMultimap.create();

    //  上面 Class對應的實例列表
    private final TLongObjectHashMap<Instance> mInstances = new TLongObjectHashMap<Instance>();

    //  關聯的snapshot對象
    Snapshot mSnapshot;

    ...
}

Heap保存了

  • StackFrame,對應着內存中棧的一幀。
  • StackTrace,對應着內存中的棧。
  • RootObj, 根對象
  • ThreadObj ,線程
  • ClassObj ,Class信息
  • Instance,ClassObj的實例
14、HeapAnalyzer.findLeakTrace()
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {
    
    //創建最短路徑Finder
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    //查找找GcRoot到泄露對象的最短路徑
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//見15

    ...
    //封裝分析的結果,省略了
}

這一步主要是創建最短路徑Finder,然後執行findPath方法去尋找

15、ShortestPathFinder.findPath()
Result findPath(Snapshot snapshot, Instance leakingRef) {
    clearState();
    canIgnoreStrings = !isString(leakingRef);

    把GcRoot加入到隊列中
    enqueueGcRoots(snapshot);

    boolean excludingKnownLeaks = false;
    LeakNode leakingNode = null;
    //遍歷隊列,GcRoot就是加入了toVisitQueue或toVisitIfNoPathQueue其中一個
    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
      LeakNode node;
      if (!toVisitQueue.isEmpty()) {
    
        node = toVisitQueue.poll();
      } else {
        node = toVisitIfNoPathQueue.poll();
        if (node.exclusion == null) {
          throw new IllegalStateException("Expected node to have an exclusion " + node);
        }
        excludingKnownLeaks = true;
      }

      // 找到了泄露對象,結束搜索
      if (node.instance == leakingRef) {
        leakingNode = node;
        break;
      }

      if (checkSeen(node)) {
        continue;
      }
        //根據node類型分case
      if (node.instance instanceof RootObj) {
        visitRootObj(node); //見16 -------------【吸引注意力】------------!!!!!------------------------------
      } else if (node.instance instanceof ClassObj) {
        visitClassObj(node);
      } else if (node.instance instanceof ClassInstance) {
        visitClassInstance(node);
      } else if (node.instance instanceof ArrayInstance) {
        visitArrayInstance(node);
      } else {
        throw new IllegalStateException("Unexpected type for " + node.instance);
      }
    }
    return new Result(leakingNode, excludingKnownLeaks);
}

這一步首先把GcRoot加入隊列,然後遍歷隊列。僅僅這個方法看不出什麼,接着看看被調用的visitRootObj方法到底做了什麼

16、ShortestPathFinder.visitRootObj()
 private void visitRootObj(LeakNode node) {
    RootObj rootObj = (RootObj) node.instance;
    獲取Instance
    Instance child = rootObj.getReferredInstance();

    if (rootObj.getRootType() == RootType.JAVA_LOCAL) {
      Instance holder = HahaSpy.allocatingThread(rootObj);
      // We switch the parent node with the thread instance that holds
      // the local reference.
      Exclusion exclusion = null;
      if (node.exclusion != null) {
        exclusion = node.exclusion;
      }
      LeakNode parent = new LeakNode(null, holder, null, null, null);
      LeakTraceElement.Type referenceType)
      //入隊,enqueue方法內部構建parent和child關係,就是通過LeakNode對象的parent屬性關聯
      //LeakNode childNode = new LeakNode(exclusion, child, parent, referenceName, referenceType);
      enqueue(exclusion, parent, child, "<Java Local>", LOCAL);
    } else {
      enqueue(null, node, child, null, null);
    }
}

大概看了下源碼,結合第15小節的代碼,就是圖的最短路徑問題了。在遍歷過程中發現泄露對象即可結束,返回結果就是GcRoot到泄露對象的最短路徑了。

發佈了10 篇原創文章 · 獲贊 12 · 訪問量 2357
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章