查看原文
其他

查内存泄漏神器,LeakCanray原来是这样工作的

彭小铭 郭霖 2020-10-29


/   今日科技快讯   /


7月2日,三星电子公司首席执行官高东真声称,他对可折叠智能手机Galaxy Fold的过早推出负责。目前,这款可折叠智能手机仍在等待重新推出的日期。


/   作者简介   /


本篇文章来自萌新彭小铭的投稿,分享了他从源码的角度对LeakCanray工作原理的理解,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


彭小铭的博客地址:

https://juejin.im/user/58218f26a22b9d0067e1593f


/   原理   /


  1. Activity onDestroy之后将它放在一个WeakReference。

  2. 这个WeakReference关联到一个ReferenceQueue。

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

  4. 如果该Activity泄露了,Dump出heap信息,然后再去分析泄露路径。


知识要点


软引用&弱引用


软引用(SoftReference)和弱引用(WeakReference)都继承Reference。


  • 软引用:当一个对象只有软引用存在时,系统内存不足时会被gc回收。

  • 弱引用:当一个对象只有弱引用存在时,随时被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的对象包括以下几种:


  1. 虚拟机栈中引用的对象。

  2. 方法区中类静态属性引用的对象。

  3. 方法区中常量引用的对象。

  4. 本地方法栈中JNI引用的对象。


就算一个对象,通过可达性分析算法分析后,发现其是『不可达』的,也并不是非回收不可的。


一般情况下,要宣告一个对象死亡,至少要经过两次标记过程:


1、经过可达性分析后,一个对象并没有与GC Root关联的引用链,将会被第一次标记和筛选。筛选条件是此对象有没有必要执行finalize()方法。如果对象没有覆盖finalize()方法,或者已经执行过了。那就认为他可以回收了。如果有必要执行finalize()方法,那么将会把这个对象放置到F-Queue的队列中,等待执行。


2、虚拟机会建立一个低优先级的Finalizer线程执行F-Queue里面的对象的finalize()方法。如果对象在finalize()方法中可以『拯救』自己,那么将不会被回收,否则,他将被移入一个即将被回收的ReferenceQ


/   源码分析  /


首先在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监听泄露流程]



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():


 xxxxxxxxxx 
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是否可用,若可用则执行下面操作:


检测DisplayLeakActivity是否可用,若不可用则调用LeakCanaryInternals.setEnabledAsync()调用AsyncTask.THREAD_POOL_EXECUTOR这个静态异步线程池执行PackageManager.setComponentEnabledSetting()将这个Activity设置为可用。PackageManager.setComponentEnabledSetting()是IPC的阻塞操作,故作异步处理。


判断是否开启Activity内存泄露检测,若没则调用ActivityRefWatcher.install()会创建ActivityRefWatcher对象然后通过Application注册Activity的生命周期监听。


判断是否开启Fragment内存泄露检测,若没则调用FragmentRefWatcher.Helper.install(),通过Activity注册监听Fragment的生命周期,且用到了leakcanary-support-fragment包,兼容了v4的fragment。


监听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销毁生命周期,然后进行内存泄露检测操作。


检测对象弱引用关联引用队列


在之前得知在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的引用队列。


等待主线程空闲


从上一小节看到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);
  }
}


AndroidWatchExecutor的构造方法会创建主线程和和后台子线的Handler,并初始化切换到子线程Handler的最大延迟时间maxBackoffFactor和初始化时间initialDelayMillis,maxBackoffFactor值为Long.MAX_VALUE / initialDelayMillis,initialDelayMillis值为5(因为AndroidWatchExecutor是被AndroidRefWatcherBuilder.defaultWatchExecutor()创建)。


execute()里的无论waitForIdle()还是postWaitForIdle(),都是需要切换到主线程执行,而且postWaitForIdle()最终切换到waitForIdle()。


waitForIdle()监听主线程Handler消息队列空闲,只要主线程空闲就会执行postToBackgroundWithDelay()操作。


postToBackgroundWithDelay()会就首先根据重试次数计算延迟执行的时间,然后延迟切换到子线程的Handler操作,如果Retryable.run()返回Result.RETRY时,会执行postWaitForIdle()继续等待主线程再次空闲。


Retryable.run()在之前RefWatcher.ensureGoneAsync()被调用,而Retryable.run()的返回值由RefWatcher.ensureGoneAsync()返回,ensureGoneAsync()在以下的情况会返回Result.RETRY:


  1. debug模式启动时。

  2. 创建dumpHeap文件失败时。

  3. 5s后UI线程未空闲时。


泄露判断


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


上面代码执行如下:


在之前的RefWatch.watch()里生成了检测引用对象的UUID的key并关联了弱引用KeyedWeakReference对象,弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中。


removeWeaklyReachableReferences()尝试移除已经到达引用队列的弱引用,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。


gone()查看retainedKeys是否包含KeyedWeakReference的唯一key。


手动触发GC操作,gcTrigger中封装了gc操作的代码,首先会调用Runtime.getRuntime().gc()以触发系统gc操作,然后当前后台子线程sleep 100毫秒,最后调用System.runFinalization()强制系统回收没有引用的队形,这样子确保引用对象是否真的被回收了。因为在dump内存信息之前提示内存泄露的时候,希望系统经过充分gc垃圾回收,而不存在任何的误判,这是对leakcanary容错性的考虑。


再次移除不可达引用,如果引用存在了,都没有被回收则判定内存泄露。


判定泄露后调用AndroidHeapDumper.dump(),首先通过LeakDirectoryProvider的实现类DefaultLeakDirectoryProvider为.prof文件创建File,若文件创建失败也会返回RETRY_LATER让之前的AndroidWatchExecutor.execute()等待下次主线程空闲执行,它最多创建7个文件,数目超过后,删除最早创建的文件,所有文件默认保存在Download文件夹下;然后利用CountDownLatch阻塞当前后台子线程5秒并监听主线程是否空闲,若不空闲则返回RETRY_LATER,若空闲则调用android.os.Debug.dumpHprofData()生成.prof文件。


调用HeapDump.Listener分析刚生成的.prof文件。


泄露信息分析


既然判断了内存泄露,那么接下来泄露信息分析,找出泄露的对象的引用路径。


ServiceHeapDumpListener是HeapDump.Listener的实现类,在RefWatch.ensureGone()中调用了它的analyze():


@Override 

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


HeapAnalyzerService继承抽象类ForegroundService,而IntentService继承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));
    }
  }


引入HAHA库(一个heap prof堆文件分析库),将hprof文件解析成内存快照Snapshot对象进行分析。

https://github.com/square/haha


deduplicateGcRoots()使用jetBrains的THashMap(THashMap的内存占用量比HashMap小)做中转,去掉snapshot中GCRoot的重复路径,以减少内存压力。


找出泄露对象并找出泄露对象的最短路劲。


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


泄露信息展示


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在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中。


/   学习借鉴   /


使用DownCountLatch同步主线程和子线程。


这里使用CopyOnWriteArraySet解决并发读写问题。


构建者模式,代码简洁、清新,链式调用创建对象,参考RefWatcherBuilder对象。


MessageQueue.addIdleHandler(IdleHandler handler),监听线程空闲。


手动GC,参考GCTrigger.runGc()。


Reference.watch()本质上是可以监控任意对象类型的,关键在于监控的时机,像activity、service、fragmen是有生命周期的,可以在ondestroy时开始监控,其他的对象类型用户可以选择合适的时机调用该方法进行监控。注意如果首页的Activity一直不销毁(onDestroy)那么将一直无法检测到首页的调用栈的内存泄漏。


借助AsyncTask.THREAD_POOL_EXECUTOR静态线程池执行异步任务。


/   总结   /


LeakCanary的源码设计非常精妙,由于本人水平有限仅给各位提供参考,希望能够抛砖引玉。


推荐阅读:

Kotlin协程入门学习,看这一篇就足够了

View的进阶,自定义一款自带动画的雷达图

原来这些设计模式的理念都是相同的


欢迎关注我的公众号

学习技术或投稿



长按上图,识别图中二维码即可关注


Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存