性能优化(五)工具LeakCanary原理2.0之前

leakCanary

square 公司 提供的一款开源的内存泄漏检查工具,在程序中检测activity 是否被gc 回收

使用

  1. debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    
  2. 在Application / activity 中初始化

    • protected void onCreate(Bundle savedInstanceState) {
          	//.......调用install 
            LeakCanary.install(getApplication());
        }
      

原理分析

  1. 首先实现监听,通过application.registerActivityLifecycleCallbacks(lifecycleCallbacks);注册activity的事件监听,并在onDestroy执行时,获取当前activity
  2. 判断是否会产生内存泄漏
    • LeakCanary 是通过通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收
      • 构建WeakReference时传递一个ReferenceQueue
      • 如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,如果垃圾回收后一直不加入到引用队列则可能产生内存泄漏
  3. 整体流程
    • 监听activitiy的声明周期,执行Destroy后,当主线程空闲时,延迟5秒执行一次判断,判断当前activity是否被GC 回收,如果已经回收了,说明没有内存泄漏,如果还没回收,我们进一步确认,手动触发一下gc,然后再判断有没有回收,如果这次还没回收,说明Activity已经泄漏,此时将分析结果反馈出来

源码分析

install 入口

  • LeakCanary.install

  • public static RefWatcher install(Application application) {
      return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
          .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
          .buildAndInstall();
    }
    
    • build() 模式构建了一个RefWatcher
      • 其中listenerServiceClass方法绑定一个后台服务DisplayLeakService以便将泄漏分析结果反馈,也可以重写这个类,进行一些上传日志等操作
      • excludedRefs,设置白名单,发生泄漏时不提示
    • 下面看buildAndInstall() 这里面创建了RefWatcher对象,通过RefWatcher创建ActivityRefWatcher,用来监听Activity是否被释放‘
构建RefWatcher对象 & ActivityRefWatcher
  • buildAndInstall

  • public RefWatcher buildAndInstall() {
      //创建Refwatcher 实例
      RefWatcher refWatcher = build();
      if (refWatcher != DISABLED) {
        //开启LeakCanary的应用,显示其图标.
        LeakCanary.enableDisplayLeakActivity(context);
        // 调用AcivityRefWatcher.installOnIcsPlus 完成ActivityRefWatcher的创建以及监听
        ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
      }
      return refWatcher;
    }
    
1 构建RefWatcher对象
  • 调用build()构建一个RefWatcher对象 该方法内部主要配置了如下参数来构建RefWatcher对象

    • watchExecutor : 线程控制器,在 onDestroy()之后并且主线程空闲时执行内存泄漏检测

      debuggerControl: 判断是否处于调试模式,调试模式中不会进行内存泄漏检测

      gcTrigger: 用于GC

      watchExecutor首次检测到可能的内存泄漏,会主动进行GC,GC之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作

      heapDumper: dump内存泄漏处的heap信息,写入hprof文件

      heapDumpListener: 解析完hprof文件并通知DisplayLeakService弹出提醒

      excludedRefs: 排除可以忽略的泄漏路径

2 构建ActivityRefWatcher
  • 通过ActivityRefWatcher.installOnIcsPlus创建ActivityRefWatcher以便完成对activity 的监听

  • public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        //创建ActivityRefWatcher
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        //开始观察activity 并在此 实现与activity声明周期绑定
        activityRefWatcher.watchActivities();
      }
    
3 完成生命周期绑定监听
  • watchActivities

  • public void watchActivities() {
      // 为了确保没有观察多次 要将之前绑定的声明周期回调解除绑定
      stopWatchingActivities();
        // 绑定声明周期
      application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }
    //解除绑定
     public void stopWatchingActivities() {
       application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
     }
    
    • 通过Application.registerActivityLifecycleCallbacks(lifecycleCallbacks);来绑定activity的生命周期

    • registerActivityLifecycleCallbacks

    • 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) {
               // 绑定 destroy 声明周期 当activity 执行destroy时 将该destroy加入一个缓存队列
               ActivityRefWatcher.this.onActivityDestroyed(activity);
             }
           };
      
    • 可以看到上面只实现了对activity的destroy方法的监听,当监听到destroy事件之后就要分析当前activity是否被销毁了

4 判断是否被GC回收
  • 通过ActivityRefWatcher.this.onActivityDestroyed(activity);方法将activity传递

  • void onActivityDestroyed(Activity activity) {
       refWatcher.watch(activity);
     }
     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();
       // 生成key
       String key = UUID.randomUUID().toString();
       //添加到retainedKeys set集合
       retainedKeys.add(key);
       // 将activity 和 引用队列 key 关联起来包装成一个弱引用
       // 弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue
       final KeyedWeakReference reference =
           new KeyedWeakReference(watchedReference, key, referenceName, queue);
     	//开始检测对应的activity  第一个参数为开始时间
       ensureGoneAsync(watchStartNanoTime, reference);
     }
    
    • retainedKeys 存贮了所有销毁的activity
5 开启线程异步执行判断
  • ensureGoneAsync

  • private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { 	// 通过watchExecutor执行异步任务了  watchExecutor.execute(new Retryable() {    @Override public Retryable.Result run() {      //判断是否被回收      return ensureGone(reference, watchStartNanoTime);    }  });}
    
    • 通过watchExecutor来执行异步任务,这里的watchExecutor是个接口,实现类是AndroidWatchExecutor

    • AndroidWatchExecutor

      • 官方注释,此执行器等待主线程空闲,然后以延迟的方式向串行后台线程发送POST
      • 内部维护两个handler
        • mainHandler 主线程handler
        • backgroundHandler 后台线程handler
    • execute

      • @Override public void execute(Retryable retryable) {   if (Looper.getMainLooper().getThread() == Thread.currentThread()) {     waitForIdle(retryable, 0);   } else {     postWaitForIdle(retryable, 0);   } } //postWaitForIdleprivate void postWaitForIdle(final Retryable retryable, final int failedAttempts) {    mainHandler.post(new Runnable() {      @Override public void run() {        waitForIdle(retryable, failedAttempts);      }    });  }
        
      • 由上面代码可知无论当前是否主线程,都需要到主线程中执行waitForIdle(retryable, failedAttempts);

      • void waitForIdle(final Retryable retryable, final int failedAttempts) {    //这需要从主线程调用。    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {      @Override public boolean queueIdle() {        postToBackgroundWithDelay(retryable, failedAttempts);        return false;      }    });  }
        
        • 设置IdleHandler()就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法
      • postToBackgroundWithDelay

      • private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {   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();       if (result == RETRY) {         postWaitForIdle(retryable, failedAttempts + 1);       }     }   }, delayMillis); }
        
      • 延时5秒执行retryablerun(),这里是通过backgroundHandler的post执行,所以run是在子线程执行的。这里的retryable就是前面execute传过来的:

6.判断过程
  • 由上可知最终用是执行到了ensureGone

  • 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;    }    //第二处代码 判断activity 是否被回收    if (gone(reference)) {      return DONE;    }    // 第三处 若没回收则调用gc    gcTrigger.runGc();    //第四处 -》再次执行第一处代码,移除已经回收掉的对象    removeWeaklyReachableReferences();    // 第五处 -》再次判断 对象是否被回收 若没回收则 开启DumpHeap并分析找到内存泄漏的地方返回    if (!gone(reference)) {      long startDumpHeap = System.nanoTime();      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);	 // 第六处,生成heapDumpFile 并分析      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;  }
    
  • 观察第一处代码

    • removeWeaklyReachableReferences

    • private void removeWeaklyReachableReferences() {  KeyedWeakReference ref;  while ((ref = (KeyedWeakReference) queue.poll()) != null) {    retainedKeys.remove(ref.key);  }}
      
      • 上面我们介绍过,当弱引用对象被回收则会放到关联的引用队列中,在第四步判断是否被GC回收的代码里面我们知道retainedKeys这个set集合,存储了所有销毁的activity对象
      • 在这取出引用队列中所有的Reference,用他们的keyretainedkeys中的key做匹配若匹配到则说明该activity已经被回收了就从retainedKeys中移除该key
  • 第二处代码

    • gone(reference)

    • private boolean gone(KeyedWeakReference reference) {  return !retainedKeys.contains(reference.key);}
      
    • 将传递进来的referencekey和清理之后的retainedKeys匹配,若匹配到则说明该reference持有的activity没有被回收,若没有回收则执行第三处 手动调用GC

  • 第三处代码 调用GC 等待100 毫秒

    • 手动调用Gc
    • 等待100毫秒之后
  • 到了第四处,再次执行第一处代码执行过后

  • 到了第五处,再次执行第二处代码,此时若还没有被回收则发生内存泄漏

  • 执行第六处代码 生成heapDumpFile 分析内存泄漏的地方

源码分析总结

  1. 首先创建RefWatcher和ActivityRefWatcher
  2. 完成生命周期绑定
  3. 在Activity destroy时,通过watcher.execute 开启了一个闲时等待任务,延迟5秒来分析当前销毁的activity是否被释放,
    • 首先检测引用队列,结合retainedKeys清空已经回收的对象
    • 判断传递进来的对象是否被回收 调用gone方法 判断retainedKeys中是否存在传递进来的对象对应的key
      • 若不存在说明回收了,返回DONE
      • 若存在说明没回收,则手动触发GC
        • 等待100毫秒之后,再次检测引用队列清空已经回收的对象
        • 再次判断传递进来的对象是否被回收
          • 若被回收则返回done
          • 若没被回收则说明泄漏了, 生成heapDumpFile,调用heapdumpListener.analyze分析内存泄漏产生的原因和位置

分析内存泄漏过程

上文中知道,当在retainedkeys中发现,未被回收的对象则床车工*.hprof文件并执行分析代码

如下

if (!gone(reference)) {  // 第一处 若发下未被回收的对象,则将堆信息保存到文件   File heapDumpFile = heapDumper.dumpHeap();  //第二处 调用HeapDuamListener.analyze 来执行分析    // 生成HeapDump对象传入当前堆栈信息文件和refreences的key ,name,和被忽略的内存泄漏的白名单   heapdumpListener.analyze(       new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,           gcDurationMs, heapDumpDurationMs)); }
生成堆栈信息文件
  • 调用heapDumper.dumpHeap();将堆信息保存到文件中
开始分析
生成HeapDump对象
  • 生成HeapDump对象,根据堆信息文件,传递的判断对象的key,name, 以及忽略产生内存泄漏的白名单

    • new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,              gcDurationMs, heapDumpDurationMs)
      
调用analyze方法分析
  • 调用analyze方法传递HeapDump对象进行分析

    • heapdumpListener.analyze(          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,              gcDurationMs, heapDumpDurationMs));
      
    • heapdumpListener 是个接口

      • /**接收要分析的堆转成的文件 */public interface Listener {  Listener NONE = new Listener() {    @Override public void analyze(HeapDump heapDump) {    }  };  void analyze(HeapDump heapDump);}
        
      • 该接口的实现类是ServiceHeapDumpListener所以analyze的方式实现如下

      • @Override public void analyze(HeapDump heapDump) {  checkNotNull(heapDump, "heapDump");  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);}
        
      • 可以看到是通过HeapAnalyzerServicerunAnalysis执行的分析

        • HeapAnalyzerService是一个IntentServes,在单独的进程中运行,避免内存不足
      • runAnalysis方法 开启新进程分析 内存泄漏

      • 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);  // 启动当前Serves执行当前Service中重写的onHandleIntent  context.startService(intent);}
        
      • onHandleIntent

      • @Override protected void onHandleIntent(Intent intent) {  String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);  //获取堆文件信息  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);	//创建HeapAnalyzer对象并注册内存泄漏白名单  HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);	// 调用CheckForLeak 分析内存泄漏  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);   //发送分析结果  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);}
        
        • HeapAnalyzer严重内存泄漏的真实性
        • HeapAnalyzer.checkForLeak是真正分析内存泄漏的入口
分析DumpFile
  • 由上面最后一步可知,通过checkForLeak分析内存泄漏

  • //在堆文件中搜索具有传入对象的Key的对应WeakReference实例,然后计算从该实例到GC根的最短强引用路径public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {    long analysisStartNanoTime = System.nanoTime();    try {      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);      HprofParser parser = new HprofParser(buffer);      //1.将堆文件转换为Snapshot      Snapshot snapshot = parser.parse();      //2.去除重复的内存泄漏      deduplicateGcRoots(snapshot);	 //3 查找对应key的 泄漏      Instance leakingRef = findLeakingReference(referenceKey, snapshot);      // 为空说明被释放了      if (leakingRef == null) {        return noLeak(since(analysisStartNanoTime));      }	 //4 不为空则 找到泄漏对象最短引用路径路径并返回      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);    } catch (Throwable e) {      return failure(e, since(analysisStartNanoTime));    }  }
    
    • findLeakingReference方法
      • 根据传递的key 去snapshot中匹配 若有匹配的,则说明发生内存泄漏
      • 若没有匹配到 则说明已经回收了
    • findLeakTrace
      • 获取泄漏对象的GcRoot 引用链

分析总结

  • 发生内存泄漏首先将堆转文件(*.hprof)
  • 创建HeapDump 对象,调用anayls开启新的进程分析对文件
  • 设置泄漏白名单
  • 将堆文件转为snapshot
  • 用传递的对象的key去snapshot中去查找受否存在具有相同的key的对象
    • 若有则说明对象没有被回收,产生内存泄漏,查找到最短的引用链并返回
    • 若没有则说明对象被回收,不产生内存泄漏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值