LeakCanray源碼分析

本文已在公衆號郭霖原創發佈。版權歸微信公衆號郭霖全部,未經許可,不得以任何形式轉載,微信文章地址:java

mp.weixin.qq.com/s/PgY_ZVsqL…android

1.原理

1.Activity onDestroy以後將它放在一個WeakReference。git

2.這個WeakReference關聯到一個ReferenceQueue。github

3.查看ReferenceQueue是否存在Activity的引用。算法

4.若是該Activity泄露了,Dump出heap信息,而後再去分析泄露路徑。微信

知識要點
  • 軟引用&弱引用併發

    軟引用(SoftReference)和弱引用(WeakReference)都繼承Reference。app

    軟引用:當一個對象只有軟引用存在時,系統內存不足時會被gc回收。dom

    弱引用:當一個對象只有弱引用存在時,隨時被gc回收。異步

    對象被回收後,Java虛擬機就會把這個引用加入到與之關聯的引用隊列中。

//java.lang.ref.Reference.java
public abstract class Reference<T> {
  ...
  volatile T referent;
  final ReferenceQueue<? super T> queue;
  ...
  Reference(T referent) {
      this(referent, null);
  }
  Reference(T referent, ReferenceQueue<? super T> queue) {
      this.referent = referent;
      this.queue = queue;
  }
  ...
  /** * <p> This method is invoked only by Java code; when the garbage collector * enqueues references it does so directly, without invoking this method. *(這個方法僅會被java代碼調用,當GC時會直接把referent添加到queue引用隊列) * */
  public boolean enqueue() {
     return queue != null && queue.enqueue(this);
  }
}
複製代碼

簡單實例:

public class RefTest {
    public static void main(String[] args) {
        //user爲強引用
        User user = new User("張三", 18);
        //建立弱引用並關聯引用隊列
        ReferenceQueue<User> queue = new ReferenceQueue<>();
        WeakReference<User> weakReference = new WeakReference<User>(user,queue);
        //置空強引用,觸發GC
        user=null;
        Runtime.getRuntime().gc();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //強制系統回收已經沒有強引用的對象
        System.runFinalization();

        WeakReference pollRef=null;
        //彈出對列的引弱用
        while ((pollRef = (WeakReference) queue.poll()) != null) {
          System.out.println("pollRef的內存地址:"+pollRef.toString());
          System.out.println("pollRef等於weakReference?:"+weakReference.equals(pollRef));
        }
    }
}
複製代碼

運行結果(注意內存地址是由JVM分配的,故可能有所差別):

pollRef的內存地址:java.lang.ref.WeakReference@610455d6 pollRef等於weakReference?:true

  • Java垃圾回收(GC) 在Java中垃圾判斷方法是 可達性分析算法,這個算法的基本思路是經過一系列的"GC Root"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑成爲引用鏈,當一個對象到GC Root沒有任何引用鏈相連時,則證實此對象是不可用的。 GC Root的對象包括如下幾種:

    一、虛擬機棧中引用的對象。

    二、方法區中類靜態屬性引用的對象。

    三、方法區中常量引用的對象。

    四、本地方法棧中JNI引用的對象。

就算一個對象,經過可達性分析算法分析後,發現其是『不可達』的,也並非非回收不可的。 通常狀況下,要宣告一個對象死亡,至少要通過兩次標記過程:

一、通過可達性分析後,一個對象並無與GC Root關聯的引用鏈,將會被第一次標記和篩選。篩選條件是此對象有沒有必要執行finalize()方法。若是對象沒有覆蓋finalize()方法,或者已經執行過了。那就認爲他能夠回收了。若是有必要執行finalize()方法,那麼將會把這個對象放置到F-Queue的隊列中,等待執行。

二、虛擬機會創建一個低優先級的Finalizer線程執行F-Queue裏面的對象的finalize()方法。若是對象在finalize()方法中能夠『拯救』本身,那麼將不會被回收,不然,他將被移入一個即將被回收的ReferenceQueue。

2.源碼分析

首先在gradle引入依賴

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

Leakcanary監聽泄露流程

2.1 Application啓動時註冊Activity生命週期監聽

LeakCanary在Application初始化,代碼以下:

public class BaseApplication extends Application {
      @Override
      public void onCreate() {
          super.onCreate();
          //檢查當前進程是否在HeapAnalyzerService所屬進程
          if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
          }
        //安裝泄露檢測
        LeakCanary.install(this);
      }
  }
複製代碼

首先調用isInAnalyzerProcess()來判斷當前進程是否爲HeapAnalyzerService運行的進程。這個方法回調用LeakCanaryInternals.isInServiceProcess()經過PackageManager、ActivityManager以及android.os.Process來判斷當前進程是否爲HeapAnalyzerService運行的進程,這樣子作的目的是不影響主進程的使用。下面是debug生成的AndroidManifest.xml,能夠在run應用以後再app/build/intermediates/instant_app_manifest/debug/查看。

<!-- 這個是LeakCanary分析泄露的Service -->
  <service android:name="com.squareup.leakcanary.internal.HeapAnalyzerService" android:enabled="false" android:process=":leakcanary" />
  <!-- 這個是LeakCanary展現泄露的Service -->
  <service android:name="com.squareup.leakcanary.DisplayLeakService" android:enabled="false" android:process=":leakcanary" />
  <!-- 這個是LeakCanary顯示泄露信息的Activity,由於被設置爲Launcher,並設置了金絲雀的icon,因此使用LeakCanar纔會在桌面上生成icon入口的緣由。 -->
  <activity android:name="com.squareup.leakcanary.internal.DisplayLeakActivity" android:enabled="false" android:icon="@mipmap/leak_canary_icon" android:label="@string/leak_canary_display_activity_label" android:process=":leakcanary" android:taskAffinity="com.squareup.leakcanary.com.pengguanming.studydemo" android:theme="@style/leak_canary_LeakCanary.Base" >
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>
複製代碼

注意上面關於LeakCanary的組件的android:enabled=false,android:enabled表示是否可以實例化該應用程序的組件,若是爲true,每一個組件的enabled屬性決定那個組件是否能夠被 enabled。若是爲false,它覆蓋組件指定的值;全部組件都是disabled。

這裏回過來看install()方法,它調用返回RefWatcher對象,這個對象經過Application註冊了Activity的生命週期監聽、經過Activity註冊監聽Fragment的生命週期,且用到了leakcanary-support-fragment包,兼容了v4的fragment。

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())//默認過濾一些系統bug形成的內存泄露
        .buildAndInstall();
  }
複製代碼

值得注意的是LeakCanary.refWatcher(application)返回的是一個AndroidRefWatcherBuilder對象,下面看看它的buildAndInstall():

if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      //判斷是否開啓內存泄露提示
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      //判斷是否開啓Activity內存泄露檢測
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      //判斷是否開啓Fragment內存泄露檢測
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    //複製給全局靜態變量,防止二次調用
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
複製代碼

首先判斷refWatcher是否已被初始,build()建立RefWatcher對象,而後判斷這對象是否是RefWatch是否可用,若可用則執行下面操做:

1.檢測DisplayLeakActivity是否可用,若不可用則調用LeakCanaryInternals.setEnabledAsync()調用AsyncTask.THREAD_POOL_EXECUTOR這個靜態異步線程池執行PackageManager.setComponentEnabledSetting()將這個Activity設置爲可用。PackageManager.setComponentEnabledSetting()是IPC的阻塞操做,故做異步處理。

2.判斷是否開啓Activity內存泄露檢測,若沒則調用ActivityRefWatcher.install()會建立ActivityRefWatcher對象而後經過Application註冊Activity的生命週期監聽。

3.判斷是否開啓Fragment內存泄露檢測,若沒則調用FragmentRefWatcher.Helper.install(),經過Activity註冊監聽Fragment的生命週期,且用到了leakcanary-support-fragment包,兼容了v4的fragment。

2.2 監聽Activity/Fragment的銷燬

上面提到Activity和Fragment的生命週期監聽,這裏首先看看監聽Activity的代碼:

//ActivityRefWatcher.java
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);
  }
複製代碼

上面經過Application註冊了ActivityRefWatcher成員lifecycleCallbacks監聽Activity生命週期回調,lifecycleCallbacks是繼承Application.ActivityLifecycleCallbacks的抽想類,這樣就完成了Activity銷燬時監聽監聽回調,並執行Activity內存泄露檢測操做。下面看看它的代碼實現:

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

如今再來看看監聽Fragment的代碼:

//FragmentRefWatcher.Helper.install()
public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
      if (SDK_INT >= O) {
        //添加兼容android O的Fragment泄露檢測
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }
      //添加經過反射構造兼容android O如下的Fragment泄露檢測
      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);
      //註冊監聽Activity生命週期回調
      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }
複製代碼

上面代碼實現和監聽Activity生命週期有所差別,首先建立FragmentRefWatcher的容器,判斷SDK版本是否大於等於Android O,若大於等於則建立AndroidOFragmentRefWatche加入容器,而後在經過反射建立SupportFragmentRefWatcher也加入到容器中,以後建立Helper並經過Application註冊了Helper成員activityLifecycleCallbacks監聽Activity的生命週期,但它僅監聽Activity的建立,下面來看看它的代碼:

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

經過上面的代碼能夠知道,這個activityLifecycleCallbacks在Activity建立時,遍歷以前 FragmentRefWatcher的list並調用實例中的watchFragments(),list只有兩個對象:SupportFragmentRefWatcher(兼容android O如下)和AndroidOFragmentRefWatcher(兼容android O+引入了fragment的生命週期,用戶不須要在onDestroy中自行調用),它們兩實現差很少,這裏只看SupportFragmentRefWatcher代碼:

class SupportFragmentRefWatcher implements FragmentRefWatcher {
 ...
  private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {
        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          //在Fragment的View銷燬時檢測泄露
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }

        @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          //在Fragment實例銷燬時檢測泄露
          refWatcher.watch(fragment);
        }
      };

  @Override public void watchFragments(Activity activity) {
    if (activity instanceof FragmentActivity) {
      //註冊supportFragment生命週期監聽
      FragmentManager supportFragmentManager =
          ((FragmentActivity) activity).getSupportFragmentManager();
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
    }
  }
}
複製代碼

到watchFragments()這裏發現這才和監聽Activity生命週期類似,經過Activity獲取FragmentManager註冊成員FragmentLifecycleCallbacks監聽Fragment銷燬生命週期,而後進行內存泄露檢測操做。

2.3 檢測對象弱引用關聯引用隊列

在【2.2】小節裏得知在Activity和Fragment銷燬時都會拿調用RefWatch.watch()方法,在此以前先了解一下RefWatch對象:

/
*@param WatchExecutor 用於執行檢測內存的線程控制器
*@param DebuggerControl 查詢是否正在debug中,若正在debug會不執行內存泄露的檢測判斷
*@param GcTrigger 用來觸發垃圾回收的,上面的線程控制器5s後觀察有泄露,不算泄露,必須垃圾回收後,再去觀察一次。因此最多會觀察兩次。第一次是5s後觀察,第二次是5s後在垃圾回收後觀察
*@param HeapDump dump出內存泄露的heap堆文件
*@param HeapDump.Listener 產生heap文件的回調
*@param ExcludeRefs 過濾掉的內存泄露
/ 
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
      HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
    this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
    this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
    this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
    this.heapDumper = checkNotNull(heapDumper, "heapDumper");
    this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
    this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
    //持有待檢測內存泄露引用的key,這裏使用CopyOnWriteArraySet解決併發讀寫問題
    retainedKeys = new CopyOnWriteArraySet<>();
    //引用隊列,弱引用或軟引用被gc回收後會達到此隊列
    queue = new ReferenceQueue<>();
  }
複製代碼

上面代碼讓咱們有個大概認識,這小節只關注retainedKeys和queue成員變量就夠了,其餘成員變量會在以後分析到,咱們先看看RefWatch.watch():

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    //檢查泄露開始時間
    final long watchStartNanoTime = System.nanoTime();
    //爲引用生成惟一的key
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    //建立一個弱引用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //開啓異步線程分析弱引用
    ensureGoneAsync(watchStartNanoTime, reference);
  }
複製代碼

首先會判斷當前RefWatch是否可用,而後對檢測對象和對應的引用標識字符串判空,生成檢查泄露開始時間,接下來爲引用生成惟一的key並添加到retainedKeys,而後建立KeyedWeakReference對象,開啓異步線程分析弱引用。這裏看看KeyedWeakReference代碼:

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");
  }
}
複製代碼

從上面能夠看出KeyedWeakReference封裝了引用惟一的key和引用標識字符串,並將檢測對象的弱引用關聯到RefWatch的引用隊列。

2.4 等待主線程空閒

從上一小節看到RefWatch.ensureGoneAsync(),下面看看代碼實現:

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

有上面代碼看出將泄露檢測轉移到WatchExecutor,而它的實現是AndroidWatchExecutor,接下來看看它的代碼:

public final class AndroidWatchExecutor implements WatchExecutor {
  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";//子線程名稱
  private final Handler mainHandler;//綁定主線程的Handler
  private final Handler backgroundHandler;//綁定子線程的Handler
  private final long initialDelayMillis;//初始化延遲的毫秒數
  private final long maxBackoffFactor;//最大失敗重試數

  public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

  @Override public void execute(@NonNull Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      //主線程操做
      waitForIdle(retryable, 0);
    } else {
      //子線程切換到主線程操做
      postWaitForIdle(retryable, 0);
    }
  }

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    //調用mainHandler將操做切換到主線程
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.(這裏必定要在主線程調用)
    //添加主線程空閒回調監聽執行postToBackgroundWithDelay()
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    //指數2增加的重試數,最大值不超過maxBackoffFactor
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    //延遲執行的時間
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    //切換到後臺子線程執行
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        //若是返回值爲RETRY時,會再次延時再次嘗試執行。延遲初始時間爲5s,之後每次重試時間x2
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }
}
複製代碼

1.AndroidWatchExecutor的構造方法會建立主線程和和後臺子線的Handler,並初始化切換到子線程Handler的最大延遲時間maxBackoffFactor和初始化時間initialDelayMillis,maxBackoffFactor值爲Long.MAX_VALUE / initialDelayMillis,initialDelayMillis值爲5(由於AndroidWatchExecutor是被AndroidRefWatcherBuilder.defaultWatchExecutor()建立)。

2.execute()裏的不管waitForIdle()仍是postWaitForIdle(),都是須要切換到主線程執行,並且postWaitForIdle()最終切換到waitForIdle()。

3.waitForIdle()監聽主線程Handler消息隊列空閒,只要主線程空閒就會執行postToBackgroundWithDelay()操做。

4.postToBackgroundWithDelay()會就首先根據重試次數計算延遲執行的時間,而後延遲切換到子線程的Handler操做,若是Retryable.run()返回Result.RETRY時,會執行postWaitForIdle()繼續等待主線程再次空閒。

5.Retryable.run()在以前RefWatcher.ensureGoneAsync()被調用,而Retryable.run()的返回值由RefWatcher.ensureGoneAsync()返回,ensureGoneAsync()在如下的狀況會返回Result.RETRY:

A.debug模式啓動時。

B.建立dumpHeap文件失敗時(見【2.5.6泄漏判斷】)。

C.5s後UI線程未空閒時(見【2.5.6泄漏判斷】)。

2.5 泄露判斷

RefWatch.ensureGone()主要是判斷內存泄露,下面看看它的實現:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    //計算watch方法到gc垃圾回收的時長
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //嘗試移除已經到達引用隊列的弱引用
    removeWeaklyReachableReferences();
    //判斷是否在debug
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks. (debug能夠創造錯誤的內存泄露)
      return RETRY;
    }
    if (gone(reference)) {//若當前對象已經可達了,即不會形成你內存泄露
      return DONE;
    }
    //手動gc,確保引用對象是否真的被回收了。由於在dump內存信息以前提示內存泄露的時候,但願系統通過充分gc垃圾回收,而不存在任何的誤判,對leakcanary容錯性的考慮
    gcTrigger.runGc();
    //清除已經到達引用隊列的弱引用
    removeWeaklyReachableReferences();
    if (!gone(reference)) {//此時對象還沒到達對列,表明已經內存泄露了
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      //dump出內存泄露的heap文件,這裏可能觸發GC
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.(不能dump heap堆文件)
        return RETRY;
      }
      //dump heap文件的時間計算
      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;
  }
複製代碼

上面代碼執行以下:

1.在以前的RefWatch.watch()裏生成了檢測引用對象的UUID的key並關聯了弱引用KeyedWeakReference對象,弱引用與ReferenceQueue聯合使用,若是弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中。

2.removeWeaklyReachableReferences()嘗試移除已經到達引用隊列的弱引用,會對應刪除KeyedWeakReference的數據。若是這個引用繼續存在,那麼就說明沒有被回收。

3.gone()查看retainedKeys是否包含KeyedWeakReference的惟一key。

4.手動觸發GC操做,gcTrigger中封裝了gc操做的代碼,首先會調用Runtime.getRuntime().gc()以觸發系統gc操做,而後當先後臺子線程sleep 100毫秒,最後調用System.runFinalization()強制系統回收沒有引用的隊形,這樣子確保引用對象是否真的被回收了。由於在dump內存信息以前提示內存泄露的時候,但願系統通過充分gc垃圾回收,而不存在任何的誤判,這是對leakcanary容錯性的考慮。

5.再次移除不可達引用,若是引用存在了,都沒有被回收則斷定內存泄露。

6.斷定泄露後調用AndroidHeapDumper.dump(),首先經過LeakDirectoryProvider的實現類DefaultLeakDirectoryProvider爲.prof文件建立File,若文件建立失敗也會返回RETRY_LATER讓以前的AndroidWatchExecutor.execute()等待下次主線程空閒執行,它最多建立7個文件,數目超事後,刪除最先建立的文件,全部文件默認保存在Download文件夾下;而後利用CountDownLatch阻塞當先後臺子線程5秒並監聽主線程是否空閒,若不空閒則返回RETRY_LATER,若空閒則調用android.os.Debug.dumpHprofData()生成.prof文件。

7.調用HeapDump.Listener分析剛生成的.prof文件。

2.6 泄露信息分析

既然判斷了內存泄露,那麼接下來泄露信息分析,找出泄露的對象的引用路徑。 ServiceHeapDumpListener是HeapDump.Listener的實現類,在RefWatch.ensureGone()中調用了它的analyze():

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

HeapAnalyzerService繼承抽象類ForegroundService,而ForegroundService繼承IntentService,它的runAnalysis()會回調onHandleIntent():

public final class HeapAnalyzerService extends ForegroundService implements AnalyzerProgressListener {
  ...
  public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    //開啓Service實例化
    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);
  }
  ...
  @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 heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    //檢查內存泄露
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    //回調結果顯示
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}
複製代碼

HeapAnalyzer的checkForLeak():是leakcannary最核心的方法

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 {
      //封裝成MemoryMappedFileBuffer對象
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //傳進HprofParser解析器
      HprofParser parser = new HprofParser(buffer);
      //裝換成Snapshot內存快照對象
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      //去重複路徑結果
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      deduplicateGcRoots(snapshot);
      //根據要檢測的key查詢解析結果中是否有須要的對象
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);
      // False alarm, weak reference was cleared in between key check and heap dump.(誤報,弱引用在對應的KeyedWeakReference檢查和heap堆文件期間被清除)
      if (leakingRef == null) {//引用對象不存在,說明gc時被清除
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
      //找出內存泄露的路徑
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
複製代碼

1.引入HAHA庫(一個heap prof堆文件分析庫),將hprof文件解析成內存快照Snapshot對象進行分析。

github.com/square/haha

2.deduplicateGcRoots()使用jetBrains的THashMap(THashMap的內存佔用量比HashMap小)作中轉,去掉snapshot中GCRoot的重複路徑,以減小內存壓力。

github.com/JetBrains/i…

3.找出泄露對象並找出泄露對象的最短路勁。

HeapAnalyzer.findLeakingReference()主要做用是找出泄露對象:

private Instance findLeakingReference(String key, Snapshot snapshot) {
   //經過查找的弱引用建立ClassOb對象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      String keyCandidate = asString(fieldValue(values, "key"));
      if (keyCandidate.equals(key)) {//key值相等則找到內存泄露對象
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }
複製代碼

1.在snpashot內存快照中找到泄露對象的弱引用。

2.遍歷這個對象全部實例。

3.若這個key值和最開始定義封裝KeyedWeakReference的key值相同,那麼返回這個泄露對象。

HeapAnalyer.findLeakTract()主要做用是找到最短泄露路徑,計算泄露大小做爲結果反饋:

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef, boolean computeRetainedSize) {
    //分析snpashot內存快照找出內存泄露的點,判斷依據是GCRoot
    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    //查找泄露的最短引用鏈,這裏只關注GCRoot的兩種類型:1.靜態;2.被其餘線程使用而且其餘線程正在運行沒有結束
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    String className = leakingRef.getClassObj().getClassName();
    // False alarm, no strong reference path to GC Roots.(誤報,沒有強引用的GCRoots)
    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));
  }
複製代碼
2.7泄露信息展現

在【2.6泄露信息分析】裏HeapAnalyzerService.onHandleIntentInForeground():

...
//HeapAnalyzerService.java
//回調結果顯示
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
複製代碼

AbstractAnalysisResultService.sendResultToListener()經過調用AnalyzedHeap.save()將以前的.prof文件保存爲.result文件,而後將.result文件路徑經過intent傳到DisplayLeakService(繼承AbstractAnalysisResultService,而AbstractAnalysisResultService是繼承ForegroundService),這裏首先調用父類AbstractAnalysisResultService.onHandleIntentInForeground()經過AnalyzedHeap.load()將.result文件生成AnalyzedHeap對象,以後調用onHeapAnalyzed()將AnalyzedHeap的信息經過Notification展現。

DisplayLeakActivity是平時用到的經過桌面入口進入的泄漏信息查看Activity在【見2.1】AndroidRefWatcherBuilder.buildAndInstall()被開啓實例化

//DisplayLeakActivity.java
  @Override protected void onResume() {
    super.onResume();
    LoadLeaks.load(this, getLeakDirectoryProvider(this));
  }
複製代碼

在onResume的時候使用了LoadLeaks(實現了Runnable接口),並傳入一個Provider,這個Provider就是上面建立.result文件時所用到的DefaultLeakDirectoryProvider,而在load方法主要在線程池執行是讀取.result文件,而後經過UI Handler將讀取的信息更新ui中。

3 學習借鑑

A.使用DownCountLatch同步主線程和子線程,見【2.5 泄漏判斷】。

B.這裏使用CopyOnWriteArraySet解決併發讀寫問題。

retainedKeys = new CopyOnWriteArraySet<>();

C.構建者模式,代碼簡潔、清新,鏈式調用建立對象,參考RefWatcherBuilder對象。

D.MessageQueue.addIdleHandler(IdleHandler handler),監聽線程空閒。

E.手動GC,參考GCTrigger.runGc()。

F.Reference.watch()本質上是能夠監控任意對象類型的,關鍵在於監控的時機,像activity、service、fragmen是有生命週期的,能夠在ondestroy時開始監控,其餘的對象類型用戶能夠選擇合適的時機調用該方法進行監控。注意若是首頁的Activity一直不銷燬(onDestroy)那麼將一直沒法檢測到首頁的調用棧的內存泄漏

G.藉助AsyncTask.THREAD_POOL_EXECUTOR靜態線程池執行異步任務。

4 總結

LeakCanary的源碼設計很是精妙,因爲本人水平有限僅給各位提供參考,但願可以拋磚引玉,若是有什麼能夠討論的問題能夠在評論區留言或聯繫本人。

參考:

mp.weixin.qq.com/s/WsG9AsmOu…

www.jianshu.com/p/49239eac7…

相關文章
相關標籤/搜索