LeakCanary原理

前言

Leakcanary是由Square公司開源的一款輕量的第三方檢測內存泄露的工具java

主要原理 watch一個即將要銷燬的對象,好比監控一個activity處於什麼狀態。android

先來看一下java內存中幾個比較重要的部分git

  • 棧(stack) 存放基本類型的數據和對象的引用,但對象自己不存放在棧中,而是存放在堆中github

  • 堆(heap) 主要存放用new產生的數據,是垃圾回收器主要回收的部分app

  • 方法區 存儲每一個類的信息(包括類的名稱、方法信息、字段信息)靜態變量、常量以及編譯器變異後的的代碼等dom

爲何會產生內存泄露異步

當一個對象已經再也不使用了,本應該回收,可是一個能到達GCRoot的對象還持有它的引用,致使它沒法被回收,還停留在堆內存中,致使內存泄漏ide

LeakCanary原理:函數

  • 當一個Activity Destory以後,將它放在一個WeakReference弱引用中中
  • 把這個WeakReference關聯到一個ReferenceQueue
  • 查看ReferenceQueue中是否存在Activity的引用
  • 若是Activity泄露了,就Dump出heap信息,而後去分析內存泄露的路徑

java中的4中引用類型工具

  • 強引用:不會被GC回收
  • 軟引用:內存不足的時候會被GC回收
  • 弱引用:當下次GC的時候會回收
  • 虛引用:任何狀況均可以回收

ReferenceQueue 引用隊列 軟引用和弱引用均可以和它集合使用,若是軟引用或者弱引用中的對象被垃圾回收了,java虛擬機會吧這個引用加入到與之關聯的引用隊列當中。

LeakCanary源碼分析

通常是在Application的onCreate方法中初始化

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

進入install方法

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

返回一個RefWatcher對象,這個對象是用來監視應該成爲弱引用的對象。最終經過buildAndInstall()這個方法建立出來。

public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    //若是是在別的進程中,會跟DISABLED相等
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      //默認爲true
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      //默認爲true
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }
複製代碼

經過build方法建立出RefWatcher,若是是別的進程,就直接返回成員變量DISABLED,若是不是建立新的RefWatcher並返回。

若是容許顯示內存泄露的Activity,就設置可顯示,DisplayLeakActivity就是當有內存泄露的時候,LeakCanary給咱們提供的可視化的那個界面

分別建立ActivityRefWatcher和FragmentRefWatcher,首先看ActivityRefWatcher.install方法

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

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }
  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
複製代碼

經過傳入的context拿到Application ,並建立ActivityRefWatcher ,最後經過application註冊Activity的生命週期回到函數,並傳入本身的callback。ActivityLifecycleCallbacksAdapter繼承自Android系統的Application.ActivityLifecycleCallbacks接口,主要爲了簡化代碼,由於只用到了onActivityDestroyed這一個方法。這個接口是Andorid系統爲咱們提供的能夠監聽到每一個Activity的生命週期。

在回調函數中能夠看到,當監聽到一個Activity銷燬的時候,就經過refWatcher.watch(activity)方法把這個Activity關聯到RefWatcher中。

在查看watch方法以前,先來看一下RefWatcher有哪些成員變量

public final class RefWatcher {

  public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();

  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final HeapDump.Listener heapdumpListener;
  private final HeapDump.Builder heapDumpBuilder;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  
  ....
複製代碼
  • WatchExecutor: 用於執行內存泄露的檢測
  • DebuggerControl: 判斷是不是調試狀態,調試狀態是不用檢測內存泄露的
  • GcTrigger: 用來處理GC,當檢測到一個對象可能會內存泄露的時候,它會調用其中的方法在手動GC一下,看是否能回收這個對象,若是不能回收那這個對象就泄露了
  • HeapDumper: dump出內存泄露的堆文件
  • HeapDump.Listener: 用來分析產生heap文件的回調
  • HeapDump.Builder:HeapDump的構建者對象
  • Set: 集合,持有待檢測的和已經產生內存泄露的引用的key
  • ReferenceQueue: 判斷弱引用所持有的對象是否執行了GC垃圾回收

OK,如今去查看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();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }
  final class KeyedWeakReference extends WeakReference<Object> {}
複製代碼

建立了一個惟一key,而後放到成員變量set中保存,以後建立了一個KeyedWeakReference這個弱引用。用來保存須要分析的對象,最後執行異步方法ensureGoneAsync來分析這個弱引用對象。

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

在子線程中執行ensureGone方法來分析對象是否真的被回收了

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    //從咱們調用watch方法到如今的總共使用的時間
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //清除set集合中 已經到達引用隊列中的弱引用
    removeWeaklyReachableReferences();
    //若是在調試狀態 就不須要分析
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //改對象沒有形成內存泄露
    if (gone(reference)) {
      return DONE;
    }
    //手動調用GC
    gcTrigger.runGc();
    //再次 清除set集合中 已經到達引用隊列中的弱引用
    removeWeaklyReachableReferences();
    //若是此時引用集合set中還包含改對象,那麼它就是個內存泄露的對象
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
     //demp出一個 .hprof文件
      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;
  }

複製代碼

總結一下前面的代碼:

  • 建立一個RefWatcher並啓動一個ActivityRefWatcher
  • 經過ActivityLifecycleCallbacks接口,監聽activity的回調,在onDestory中去將activity對象放入觀察引用中去觀察
  • 先清除引用隊列中的弱引用,接着檢查對象是否到達引用隊列,而後手動執行GC,若是GC完後還有未被回收的對象,調用analyze方法分析內存泄露
public interface Listener {
    Listener NONE = new Listener() {
      @Override public void analyze(HeapDump heapDump) {
      }
    };

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

analyze是Listener接口中的一個方法,它的實現類是在ServiceHeapDumpListener中。最後調用了HeapAnalyzerService.runAnalysis方法。

public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    ContextCompat.startForegroundService(context, intent);
  }
複製代碼

HeapAnalyzerService 繼承自 ForegroundService , ForegroundService 繼承自 IntentService,runAnalysis方法中就是開啓了一個前臺的IntentService。最後會執行IntentService的onHandleIntent方法,這裏面又執行了抽象方法onHandleIntentInForeground,這個方法在HeapAnalyzerService類中實現。

@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
複製代碼

從intent中拿到className和HeapDump,而後經過HeapAnalyzer這個類的checkForLeak方法進行分析。最後經過sendResultToListener方法返回。

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);
      //去除重複的內容
      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));
      }
      //找到泄露對象的最短路徑
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
複製代碼

checkForLeak方法就是LeakCanary中的核心方法了,這裏面用到了Square的另外一個開源庫haha庫,地址 github.com/square/haha

  • 經過HprofParser類將hprof轉換爲Snapshot內存快照。Snapshot中包含全部對象引用的路徑,就能查找到內存泄露的路徑了
  • 優化GCRoot 經過deduplicateGcRoots方法刪除重複的路徑
  • findLeakingReference找出泄露的對象
  • findLeakTrace方法找出泄露對象的最短路徑

findLeakingReference方法

private Instance findLeakingReference(String key, Snapshot snapshot) {
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    if (refClass == null) {
      throw new IllegalStateException(
          "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
    }
    List<String> keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      Object keyFieldValue = fieldValue(values, "key");
      if (keyFieldValue == null) {
        keysFound.add(null);
        continue;
      }
      String keyCandidate = asString(keyFieldValue);
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }
複製代碼
  • 在內存快照Snapshot中找到第一個弱引用KeyedWeakReference,這就是內存泄露的對象。
  • 遍歷這個對象的全部的實例
  • 若是找到的key值和最開始保存的key值同樣,那麼這個對象就是內存泄露的對象

findLeakTrace方法

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef, boolean computeRetainedSize) {

    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

    String className = leakingRef.getClassObj().getClassName();

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(className, since(analysisStartNanoTime));
    }

    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    long retainedSize;
    if (computeRetainedSize) {

      listener.onProgressUpdate(COMPUTING_DOMINATORS);
      // Side effect: computes retained size.
      snapshot.computeDominators();

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();

      // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
      if (SDK_INT <= N_MR1) {
        listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
        retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
      }
    } else {
      retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }
複製代碼

經過findPath方法,GCroot開始往下尋找

LeakTrace就是內存泄露的調用棧

getTotalRetainedSize()方法,計算內存泄露的內存空間大小

OK Activity的監控流程就看完啦,下面看一下Fragment的。其實跟Activity差很少。從install開始

public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
    // 若是大於Anroid 26,須要增長AndroidOFragmentRefWatcher
      if (SDK_INT >= O) {
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }
      // 經過反射添加SupportFragmentRefWatcher
      try {
        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
        Constructor<?> constructor =
            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
        FragmentRefWatcher supportFragmentRefWatcher =
            (FragmentRefWatcher) constructor.newInstance(refWatcher);
        fragmentRefWatchers.add(supportFragmentRefWatcher);
      } catch (Exception ignored) {
      }

      if (fragmentRefWatchers.size() == 0) {
        return;
      }

      Helper helper = new Helper(fragmentRefWatchers);

      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }
複製代碼

經過反射找到SupportFragmentRefWatcher,它類須要在build.gradle中添加debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'加入引用。

最後註冊ActivityLifecycleCallbacks,來監聽activity的回調

private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity);
            }
          }
        };
複製代碼

能夠看到這裏監聽的是activity的onActivityCreated這個生命週期函數,而後把當前的activity的對象傳入FragmentRefWatcher中,執行接口watchFragments。SupportFragmentRefWatcher和AndroidOFragmentRefWatcher是FragmentRefWatcher的實現類。最終會回調實現類的watchFragments方法

@Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }
複製代碼

經過Activity找到FragmentManager,而後註冊系統的Fragment的生命週期回調監聽

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }
        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };
複製代碼

Fragment的回調主要監聽了onFragmentViewDestroyed和onFragmentDestroyed兩個回調方法。最終都會調用RefWatcher中的watch方法,這裏面跟前面activity中是同樣的啦。

相關文章
相關標籤/搜索