LeakCanary(2.0版本之前)的使用及其原理

概述

leakcanary是一个监测android和java内存泄漏的工具。他能够在不影响程序正常运行的情况下,动态收集程序存在的内存泄漏问题。

大体流程

  1. Activity Destory之后将它放在一个WeakReference
  2. 这个WeakReference关联到一个ReferenceQueue
  3. 查看ReferenceQueue是否存在Activity的引用
  4. 如果该Activity泄漏了,Dump出heap信息,然后再去分析泄漏路径。

引用类型

  1. 强引用(StrongReference) 不管内存是否不够分配都不会清除
  2. 软引用(SoftRefererence)  当内存不够用的时候会去主动清除当前内存
  3. 弱引用(WeakReference)   当垃圾回收器检测到弱引用,不管内存够不够都会清除它
  4. 虚引用 就相当于形同虚设的引用 任何情况都会回收它

对象被垃圾回收,Java虚拟机就会把这个引用加入到与之关联的引用队列中。

LeakCanary的使用

先从低版本2.0以下的版本 当前使用的是1.5

第一步 加入依赖包

 debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5'
 releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

第二步 注册Leakcanary

public class MyApplication extends Application {
  
    @Override public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // 判断是否和 LeakCanary 初始化同一进程
            return;
        }
        LeakCanary.install(this);//获取一个 Watcher
    }
}

查看源码

public static RefWatcher install(Application application) {
    //是启动Activity的RefWatcher 然后通过Activity的RefWatcher 通过执行ondestory 之后来监测Activity内存泄漏
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

buildAndInstall 继续往下看

public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();//创建RefWatcher来启动Activity的RefWatcher 监听Activity的内存泄漏的情况
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);//这个enableDisplayLeakActivity 就是那个弹窗,告诉我们哪里有内存泄漏,开启DisplayLeakActivity
      ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
    }
    return refWatcher;
  }
  •  创建RefWatcher来启动Activity的RefWatcher 监听Activity的内存泄漏的情况
  • 这个enableDisplayLeakActivity 就是那个弹窗,告诉我们哪里有内存泄漏,开启DisplayLeakActivity

installOnIcsPlus 继续往下看

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 
    activityRefWatcher.watchActivities();
  }
  •  创建了ActivityRefWatcher 

watchActivities 继续往下看

public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();//就是反注册之前生命周期的Callback,保证之前所作的内存泄漏的工作都删除
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);//重新注册我们Activity生命周期的callback
  }
  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  • 反注册之前生命周期的Callback,保证之前所作的内存泄漏的工作都删除
  • 重新注册我们Activity生命周期的callback

接下来看一下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) {
        }
        //发现在Activity调用onDestory这个方法之后所作的操作
        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity); 
            //onActivityDestroyed这个方法会将我们之前定义好的ActivityLifecycleCallbacks 把我们Activity的ondestory这个方法以及他们的生命周期关联到了ActivityRefWatcher
        }
      };
  •  发现在Activity调用onDestory这个方法之后所作的操作
  • onActivityDestroyed这个方法会将我们之前定义好的ActivityLifecycleCallbacks 把我们Activity的ondestory这个方法以及他们的生命周期关联到了ActivityRefWatcher

onActivityDestroyed 做了哪些东西呢?

void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

在了解watch方法之前,先看一下RefWatcher 这个类的成员变量都是什么

public final class RefWatcher {

  private final WatchExecutor watchExecutor; //用于去执行我们的内存泄漏的检测用的
  private final DebuggerControl debuggerControl;//用于查询我们是否正在调试中 如果正在调试中,就不会执行内存泄漏的检测判断
  private final GcTrigger gcTrigger;//处理GC的 用于判断我们的内存泄漏之前会给我们泄漏对象再一次机会,会调用这个对象里面的方法去调用我们的gc,看它是否去gc,如果不能gc的话,就会把信息给显示出来
  private final HeapDumper heapDumper;//dumper出我们内存泄漏的堆文件
  private final Set<String> retainedKeys;//持有那些待检测的,以及那些已经产生了内存泄漏的引用的key
  private final ReferenceQueue<Object> queue;//引用队列,主要用于判断弱引用持有的对象是否已经被执行gc垃圾回收。
  private final HeapDump.Listener heapdumpListener;//主要用于分析一些产生heap文件的回调
  private final ExcludedRefs excludedRefs;//排除一些系统引起的内存泄漏
...
}

分析完成员变量接下来看一下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(); //我们要对我们的Reference的引用添加一个唯一的key值
    retainedKeys.add(key);//将我们获取到的key加入我们所说的set集合当中,也就是说加入到了我们待检测以及已经产生内存泄漏的key的set集合当中
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);//利用watch方法传入的对象,创建了我们需要的弱引用

    ensureGoneAsync(watchStartNanoTime, reference);//开启一个异步线程来分析我们刚刚所创建好的Reference弱引用
  }
  • 要对我们的Reference的引用添加一个唯一的key值
  • 将我们获取到的key加入我们所说的set集合当中,也就是说加入到了我们待检测以及已经产生内存泄漏的key的set集合当中
  • 利用watch方法传入的对象,创建了我们需要的弱引用
  • 开启一个异步线程来分析我们刚刚所创建好的Reference弱引用

接下来分析ensureGoneAsync 这个方法

 private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() { //在线程池中执行了该方法
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {//确保我们的Activity是否已经进入这个Gone这个状态,Gone这个状态表示这个Activity是否真的被回收了
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//就是用来计算我们调用watch方法到gc回收的总共产生的时间

    removeWeaklyReachableReferences();//清除此时已经到达我们的引用队列的弱引用,剩下的就是一些未被回收的引用,那么它就会通知我们这些就是我们应该回收的对象

    if (debuggerControl.isDebuggerAttached()) {//如果此时处于debug的状态下,就不会进行内存泄漏的分析
      // 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();//这个时候就会dump出我们内存泄漏的那些堆文件
      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;
  }
  •  确保我们的Activity是否已经进入这个Gone这个状态,Gone这个状态表示这个Activity是否真的被回收了
  • 清除此时已经到达我们的引用队列的弱引用,剩下的就是一些未被回收的引用,那么它就会通知我们这些就是我们应该回收的对象
  • 如果此时处于debug的状态下,就不会进行内存泄漏的分析
  • 如果当前对象它已经可达了,也就是不会造成内存泄漏,告诉我们已经不用解决这个内存泄漏了
  • 如果当前对象没有达到可达对象,来进行手动的垃圾回收
  • 再次清除已经到达我们的引用队列的弱引用
  • 如果此时对象没有到达可达,预期被垃圾回收对象可能会出现在当前的对象当中,没有出现的话则表示已经内存泄漏了
  • 内存泄漏之后就会dump出我们内存泄漏的那些堆文件
  • 分析内存泄漏

总结:

  1. 首先会创建一个Refwatcher,启动一个ActivityRefWatcher ,它会开启一个Activity的RefWatcher,就是用于监听我们Activity的回收情况
  2. 通过ActivityLifecycleCallbacks把Activity的onDestory生命周期关联
  3. 最后在线程池中去开始分析我们的泄漏

如何分析我们的内存泄漏

查看analyze

public final class HeapDump implements Serializable {
...
void analyze(HeapDump heapDump);//接口
...
}

接下来看一下具体实现

public final class ServiceHeapDumpListener implements HeapDump.Listener {

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

继续查看一下runAnalysis这个方法

public final class HeapAnalyzerService extends IntentService {
...
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);
  }
@Override 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);//分析我们的堆内存的 heapDump.excludedRefs排除系统存在的内存泄漏

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);//来分析内存使用结果
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);//调用一个回调来进行接口的显示
  }
}
  • 分析产生的堆文件
  • 分析我们的堆内存的 heapDump.excludedRefs排除系统存在的内存泄漏
  • 调用一个回调来进行接口的显示

接下来看一下checkForLeak 这个方法

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    //就是为了将hrof文件解析成我们snapshot对象    
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);//把这个hrof文件封装成这个对象
      HprofParser parser = new HprofParser(buffer);//hrof的解析器来解析上面传递过来的hrof文件的
      Snapshot snapshot = parser.parse();//具体解析工作
      deduplicateGcRoots(snapshot);//去重的GcRoots ,会对我们分析的结果进行去重,把那些重复的都删掉

      Instance leakingRef = findLeakingReference(referenceKey, snapshot);//根据我们需要检测的类的referenceKey,来查询解析我们结果中是否有我们需要的对象,获取解析结果中检测的对象就是这个方法的意义

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {//如果表示为空的话就表示GC已经清除了
        return noLeak(since(analysisStartNanoTime));
      }

      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);//如果不为空的话就会把我们的整个路径给找出来
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

总结checkForLeak

  1. 把hprof文件转为Snapshot(内存快照),由于Snapshot包含所有对象引用的路径,然后就能查找到它所需要的内存泄漏路径
  2. 优化gcroots  通过deduplicateGcRoots方法来删除重复的路径
  3. 找出泄漏的对象以及找出泄漏对象的最短路径

接下来分析findLeakingReference 如何找到内存泄漏的引用

private Instance findLeakingReference(String key, Snapshot snapshot) {
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    //创建一个calss对象,通过findClass方法通过查找弱引用的对象就可以查找出内存泄漏的对象
    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)) {//表示找到了那个检测对象
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);//添加到集合当中
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

总结findLeakingReference

  1. 在snapshot快照中找到第一个弱引用
  2. 遍历这个对象的所有实例
  3. 如果找到的key值和最开始定义封装的key值相同,那么返回这个泄漏对象

接下来分析一下findLeakTrace

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

    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); 
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    //通过分析hprof文件然后在找出内存泄漏的点
    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {//找不到内存泄漏的话,返回一个noLeak的返回值
return noLeak(since(analysisStartNanoTime));
    }

    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);//内存泄漏调用栈

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

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

    Instance leakingInstance = result.leakingNode.instance;

    long retainedSize = leakingInstance.getTotalRetainedSize();//就是用来计算内存泄漏的内存空间大小

    retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

总结findLeakTrace

  1. 解析hprof文件,把这个文件封装成snapshot
  2. 根据弱引用和前面定义的key值,确定泄漏的对象
  3. 通过key对象的引用找到最短泄漏路径,作为结果反馈出来

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万子开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值