內存泄露檢測神器 -- LeakCanary源碼分析

鑑於筆者能力有限,如有疏漏錯誤之處,敬請原諒,本文只做拋磚引玉的作用
一、內存泄露介紹
內存泄露基本上都是由於不恰當的使用,當對象使用完了之後,還存在強引用,導致該釋放的時候,沒有釋放,一直佔用內存,我想是很多人會遇到的問題,一般的解決思路是生成hprof文件,再用mat等內存分析工具來查看,找到懷疑點,然後對照着源碼再來驗證。這裏有一個很好的工具,可以在運行期間就能檢測到內存泄露,並且輸出最短路徑,它就是檢測內存泄露的神器LeakCanary。
二、leakcanary介紹
相關的使用方法可以參考:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/,裏面有詳細的介紹,使用也比較簡單。
首先在自定義的Application中onCreate中調用下面即可
public void onCreate() {
super.onCreate();
enabledStrictMode();//開啓嚴格模式
LeakCanary.install(this);
}
private void enabledStrictMode() {
if (SDK_INT >= GINGERBREAD) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}

上面的使用是不是非常簡潔,基本不需要什麼操作就可以直接使用,那麼它是怎麼實現的呢。


三、從源碼角度分析leakcanary
我們從源碼的角度來分析LeakCanary的原理,LeakCanary源碼地址:https://github.com/square/leakcanary,下面來看一下源碼的目錄結構
leakcanary
|------ leakcanary-analyzer 內存泄露文件分析器
|------ leakcanary-android 內存泄露平臺android相關的具體實現,比如生成hprof,調用內存泄露檢測,調用內存泄露分析器,以及顯示最短路徑
|------ leakcanary-android-no-op 從名字可以知道沒有做任何操作
|------ leakcanary-sample 一個使用的sample
|------ leakcanary-watcher 整個內存泄露檢測框架的抽象實現,獨立於android系統
|------ 其他
根據模塊來分析一下,具體如何實現的
1.內存泄露的檢測

上圖是leakcanary-watcher主要接口的UML類圖,比較粗糙。RefWatcherBuilder.build構造其他幾個類,作爲RefWatcher的構造參數最終生成RefWatcher。
具體過程描述一下,當需要查看某個對象是否存在內存泄露,調用RefWatcher.watch方法,然後調用RefWatcher.ensureGoneAsync來調用WatchExecutor.execute執行異步操作,該操作描述一下,首先確認該對象是否已經被釋放,如果沒有,再次調用GcTrgger.runGc進行垃圾回收,然後再次確認該對象是否已經被釋放,如果沒有,則調用HeapDumper.dumpHeap()方法,生成heapdump文件,最後交給分析器分析。
2.內存泄露分析
leakcanary-analyzer負責這一功能,內存泄露的核心類是HeapAnalyzer,流程比較簡單,但是具體實現細節相當複雜,主要用到了一個庫-com.squareup.haha用於分析hprof文件的,下面看一下核心代碼流程。
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);
    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));
  }
}
描述一下流程:
(1).首先將內存泄露文件(hprof)直接映射到內存(映射到虛擬內存),也就是下面操作
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile)
(2).解析hprof文件,生成堆快照(Snapshot),也就是下面操作,這個是根據hprof文件格式來解析的,有興趣可以自己研究一下。
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
(3).根據快照,和gc根節點的名稱,去掉重複的gc根節點,也就是下面操作
deduplicateGcRoots(snapshot);
(4).根據關鍵字,找到泄露引用實例
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
(5).然後找到內存泄露的最短強引用路徑
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
最後將結果返回。
3.內存泄露檢測、分析還有顯示的整個調用過程
我們看leakcanary-android這個工程,LeakCanary作爲入口,調用install方法
public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
上述是一個鏈式調用,refWatcher(application)初始化AndroidRefWatcherBuilder(繼承自RefWatcherBuilder),listenerServiceClass初始化dump listener即ServiceHeapDumpListener(實現HeapDump.Listener接口,當生成dump file時,將內存泄露文件扔給分析器的接口),其中DisplayLeakService(繼承自AbstractAnalysisResultService)處理分析完之後的結果的,buildAndInstall方法調用了上述RefWatcherBuilder.build方法,接着在Application中註冊監聽Activity生命週期的監聽類。
if (refWatcher != DISABLED) {
  ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
  LeakCanary.enableDisplayLeakActivity(context);
}
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
  if (SDK_INT < ICE_CREAM_SANDWICH) {
    // If you need to support Android < ICS, override onDestroy() in your base activity.
    return;
  }
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  activityRefWatcher.watchActivities();
}
註冊監聽類
public void watchActivities() {
  // Make sure you don't get installed twice.
  stopWatchingActivities();
  application.registerActivityLifecycleCallbacks(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);
      }
    };
當Activity調用destroy之後,會調用ActivityRefWatcher.this.onActivityDestroyed方法,開始監聽Activity是否有內存泄露。
void onActivityDestroyed(Activity activity) {
  refWatcher.watch(activity);
}
開始了上述描述的watch流程
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);
}
這裏需要注意的是使用了KeyedWeakReference(繼承自WeakReference)是一個弱引用,並且加入到了ReferenceQueue中,遍歷的這個隊列可以知道哪些對象是否被回收,代碼中用了一個Set來管理隊列的對象,當對象不存在,從set中去掉。
檢測過程在上面已經簡單描述過,我們看下代碼
(1)初始化RefWatcher
public final RefWatcher build() {
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }

  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();
  }

  return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
      excludedRefs);
}
(2)調用watch方法,上面有描述
(3)再次調用gc,生成dump file
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

  removeWeaklyReachableReferences();

  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  if (gone(reference)) {
    return DONE;
  }
  gcTrigger.runGc();
  removeWeaklyReachableReferences();
  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;
}
(4)扔給分析器分析
ServiceHeapDumpListener的analyze方法
public void analyze(HeapDump heapDump) {
  checkNotNull(heapDump, "heapDump");
  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
HeapAnalyzerService的runAnalysis和onHandleIntent,最後扔給了HeapAnalyzer來分析處理
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);
}

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);
}
(5)將最終的結果DisplayLeakService生成notification
AbstractAnalysisResultService調用sendResultToListener將結果扔到onHeapAnalyzed處理,onHeapAnalyzed在DisplayLeakService中實現的。
用戶點擊通知,可以啓動DisplayLeakActivity顯示內容。

鑑於筆者能力有限,分析過於粗糙,無法將精華部分呈現給讀者,讀者可以自行去查看源碼流程和查找相關文檔。源碼地址:https://github.com/square/leakcanary

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