源码学习LeakCanary内存泄漏检测流程

1、LeakCanary的使用
public class App extends Application {

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

LeakCanary.install(this)是分析的入口

2、LeakCanary.install(Application application)
public static RefWatcher install(Application application) {
    //refWatcher返回AndroidRefWatcherBuilder对象
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) //excludedRefs 排除不希望检测的对象
        .buildAndInstall(); //见3
}
  
public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
}

创建了AndroidRefWatcherBuilder对象,接着调用了buildAndInstall方法

3、AndroidRefWatcherBuilder.buildAndInstall()
  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();// 见4 调了父类RefWatcherBuilder的build方法
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);//见5
    }
    return refWatcher;
  }

创建出RefWatcher对象,接着ActivityRefWatcher去installl

4、RefWatcherBuilder.build()
 public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //下面是构建RefWatcher对象所需参数
    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      excludedRefs = defaultExcludedRefs();
    }

    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }

    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }

    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }

    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }

    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }
    //返回新建的RefWatcher实例
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }

构建了RefWatcher对象。顾名思义,RefWatcher对象是用来观察引用的

5、ActivityRefWatcher.install()
public static void install(Application application, RefWatcher refWatcher) {
    //创建ActivityRefWatcher用于检测Activity
    new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //注册activity生命周期回调
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
  new Application.ActivityLifecycleCallbacks() {
  
    ...

    @Override public void onActivityDestroyed(Activity activity) {
        //在Activity.onDestoryed回调时,运行ActivityRefWatcher.onActivityDestroyed(activity)方法
        ActivityRefWatcher.this.onActivityDestroyed(activity);//见6
    }
};

在Activity.onDestoryed回调时,运行ActivityRefWatcher.onActivityDestroyed(activity)方法

6、ActivityRefWatcher.onActivityDestroyed(activity)
void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);//见7
}

调用了refWatcher.watch()方法,refWatcher就是第5小结构建ActivityRefWatcher对象构建方法传入的

7、RefWatcher.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(); //生产和被观察关联的key
    retainedKeys.add(key);//retainedKeys保存key
    //创建KeyedWeakReference,它是WeakReference的子类,增加了key和name属性。
    //把检测对象和ReferenQueue对象也传了进去
    final KeyedWeakReference reference = 
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
    ensureGoneAsync(watchStartNanoTime, reference);//见8
}

这里注意下,RefWatcher.watch()接收到的是Object对象,也就是说LeakCanary本质上是支持所有对象泄漏检测的,在检测的地方运行RefWatcher.watch()方法即可。

8、RefWatcher.ensureGoneAsync()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);//见9
      }
    });
}

watchExecutor是一个封装了Handler、具有重试功能的执行器。传递给WatchExecutor.execute方法的Retryable对象,在经过5秒延迟后会执行run方法。(5秒延迟是跟ANR有关???),run方法调了ensureGone方法

9、RefWatcher.ensureGone()
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences();//见10 清除入队的弱引用

    if (debuggerControl.isDebuggerAttached()) {
      // debug模式下重试.
      return RETRY;
    }
    //gone方法如果检测到对象被回收,说明没有泄露
    if (gone(reference)) {//见10
      return DONE;
    }
    //触发GC
    gcTrigger.runGc();
    removeWeaklyReachableReferences();//清除入队的弱引用
    if (!gone(reference)) {//再次判断是否检测对象被回收
      //仍然没有正常回收检测对象
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      //dump出内存信息
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //构建HeapDump去分析内存泄露的详细信息
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));//见11
    }
    return DONE;
}

首先运行removeWeaklyReachableReferences清除入队的弱引用,然后gone方法判断检测对象是否被回收。若未被回收触发一次gc,接着再清除入队的弱引用。如果仍未被回收就dump出hprof文件去分析。
看下removeWeaklyReachableReferences和gone方法的实现

10、RefWatcher.gone() & removeWeaklyReachableReferences()
private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    //移除入队的Reference
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key); //retainedKeys删除关联的key
    }
}

private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
}

从queue逐个移除KeyedWeakReference对象,接着移除retainedKeys中和KeyedWeakReference对象关联的key,也就是如果检测对象已经到达过引用队列,被正常回收,retainedKeys是不再包含关联的key。所以gone方法可以通过判断retainedKeys是否包含key来确认被检测对像是否被回收。

11、ServiceHeapDumpListener.analyze()

heapdumpListener.analyze会走到HeapAnalyzerService.onHandleIntent()方法

protected void onHandleIntent(Intent intent) {
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
        
    //创建分析类
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //见12 分析流程
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //分析后
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

重点的一步调了heapAnalyzer.checkForLeak分析

12、HeapAnalyzer.checkForLeak()
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
       //MemoryMappedFileBuffer封装heapDumpFile
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //HprofParser分析hprof文件
      HprofParser parser = new HprofParser(buffer);
      //分析结果封装在Snapshot对象中
      Snapshot snapshot = parser.parse();//见13
      //去重GcRoot
      deduplicateGcRoots(snapshot);

      //通过key找到检测对象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);//见14

      // 没有泄露
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }
      //找到最短泄露路径
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); //见15
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
}

主要有几个步骤:

  1. 把heapDumpFile封装成MemoryMappedFileBuffer。
  2. 通过HprofParser对象去分析MemoryMappedFileBuffer。
  3. 去除Snapshot中重复的GcRoot。
  4. 通过key找到Snapshot中保存的泄露对象。
  5. 分析GcRoot到泄露对象的最短路径。
    checkForLeak方法有两个东西的没搞懂,一个Snapshot的数据结构,另一个是findLeakingReference方法。先看看Snapshot的数据结构。

下面信息根据debug追踪、注释、类属性和命名等方式得出的结果,未必正确,希望有大牛指点一二

13、Snapshot 数据结构
/*
 * A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
 *
 * There are three possible heaps: default, app and zygote. GC roots are always reported in the
 * default heap, and they are simply references to objects living in the zygote or the app heap.
 * During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
 */
public class Snapshot {

    private static final String JAVA_LANG_CLASS = "java.lang.Class";

    //  Special root object used in dominator computation for objects reachable via multiple roots.
    public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);

    private static final int DEFAULT_HEAP_ID = 0;

    final HprofBuffer mBuffer;

    //堆信息
    ArrayList<Heap> mHeaps = new ArrayList<Heap>();

    Heap mCurrentHeap;

    private ImmutableList<Instance> mTopSort;

    private Dominators mDominators;

    //  保存各种Reference以及它的子类的Class信息
    private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>();

    private int[] mTypeSizes;

    private long mIdSizeMask = 0x00000000ffffffffl;

    

    ....
    //省略很多代码
}

从源码上来看Snapshot主要有:

  • mHeaps,Heap的列表,有四种可能default、app、image和zygote。GcRoot总是保存在default heap,并且GcRoot仅仅是指向保存在app、zygote的对象。
  • mReferenceClasses ,保存各种Reference以及它的子类的Class信息,之前使用的KeyedWeakReference也保存在这里

接着看看Heap的数据结构

public class Heap {

    private final int mId;

    private final String mName;

    //  每个item对应着内存中栈的一帧
    TLongObjectHashMap<StackFrame> mFrames = new TLongObjectHashMap<StackFrame>();

    //  StackTrace对应着内存中的栈,每个StackTrace里面包含多个StackFrame
    TIntObjectHashMap<StackTrace> mTraces = new TIntObjectHashMap<StackTrace>();

    //  根对象,如内部字符串、jni局部变量等
    ArrayList<RootObj> mRoots = new ArrayList<RootObj>();

    //  线程列表
    TIntObjectHashMap<ThreadObj> mThreads = new TIntObjectHashMap<ThreadObj>();

    //  Class信息
    TLongObjectHashMap<ClassObj> mClassesById = new TLongObjectHashMap<ClassObj>();

    Multimap<String, ClassObj> mClassesByName = ArrayListMultimap.create();

    //  上面 Class对应的实例列表
    private final TLongObjectHashMap<Instance> mInstances = new TLongObjectHashMap<Instance>();

    //  关联的snapshot对象
    Snapshot mSnapshot;

    ...
}

Heap保存了

  • StackFrame,对应着内存中栈的一帧。
  • StackTrace,对应着内存中的栈。
  • RootObj, 根对象
  • ThreadObj ,线程
  • ClassObj ,Class信息
  • Instance,ClassObj的实例
14、HeapAnalyzer.findLeakTrace()
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {
    
    //创建最短路径Finder
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    //查找找GcRoot到泄露对象的最短路径
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//见15

    ...
    //封装分析的结果,省略了
}

这一步主要是创建最短路径Finder,然后执行findPath方法去寻找

15、ShortestPathFinder.findPath()
Result findPath(Snapshot snapshot, Instance leakingRef) {
    clearState();
    canIgnoreStrings = !isString(leakingRef);

    把GcRoot加入到队列中
    enqueueGcRoots(snapshot);

    boolean excludingKnownLeaks = false;
    LeakNode leakingNode = null;
    //遍历队列,GcRoot就是加入了toVisitQueue或toVisitIfNoPathQueue其中一个
    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
      LeakNode node;
      if (!toVisitQueue.isEmpty()) {
    
        node = toVisitQueue.poll();
      } else {
        node = toVisitIfNoPathQueue.poll();
        if (node.exclusion == null) {
          throw new IllegalStateException("Expected node to have an exclusion " + node);
        }
        excludingKnownLeaks = true;
      }

      // 找到了泄露对象,结束搜索
      if (node.instance == leakingRef) {
        leakingNode = node;
        break;
      }

      if (checkSeen(node)) {
        continue;
      }
        //根据node类型分case
      if (node.instance instanceof RootObj) {
        visitRootObj(node); //见16 -------------【吸引注意力】------------!!!!!------------------------------
      } else if (node.instance instanceof ClassObj) {
        visitClassObj(node);
      } else if (node.instance instanceof ClassInstance) {
        visitClassInstance(node);
      } else if (node.instance instanceof ArrayInstance) {
        visitArrayInstance(node);
      } else {
        throw new IllegalStateException("Unexpected type for " + node.instance);
      }
    }
    return new Result(leakingNode, excludingKnownLeaks);
}

这一步首先把GcRoot加入队列,然后遍历队列。仅仅这个方法看不出什么,接着看看被调用的visitRootObj方法到底做了什么

16、ShortestPathFinder.visitRootObj()
 private void visitRootObj(LeakNode node) {
    RootObj rootObj = (RootObj) node.instance;
    获取Instance
    Instance child = rootObj.getReferredInstance();

    if (rootObj.getRootType() == RootType.JAVA_LOCAL) {
      Instance holder = HahaSpy.allocatingThread(rootObj);
      // We switch the parent node with the thread instance that holds
      // the local reference.
      Exclusion exclusion = null;
      if (node.exclusion != null) {
        exclusion = node.exclusion;
      }
      LeakNode parent = new LeakNode(null, holder, null, null, null);
      LeakTraceElement.Type referenceType)
      //入队,enqueue方法内部构建parent和child关系,就是通过LeakNode对象的parent属性关联
      //LeakNode childNode = new LeakNode(exclusion, child, parent, referenceName, referenceType);
      enqueue(exclusion, parent, child, "<Java Local>", LOCAL);
    } else {
      enqueue(null, node, child, null, null);
    }
}

大概看了下源码,结合第15小节的代码,就是图的最短路径问题了。在遍历过程中发现泄露对象即可结束,返回结果就是GcRoot到泄露对象的最短路径了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值