LeakCanary源码分析

作用

监控内存泄露 基于MAT(内存分析工具)

分析

如何得到未回收对象?
ReferenceQueue+WeakReference+手动调用GC

WeakReference创建时传入ReferenceQueueWeakReference引用的对象生命周期结束 当GC检测到会将它加入ReferenceQueue中 GC发生后 对象一直未被加入ReferenceQueue 那就可能存在内存泄漏

怎么判断未被回收对象一定发生内存泄露?
此对象有没有被其他对象引用 找出最短引用链 VMDebug + haha(开源库)
VMDebug:有堆内各个对象的引用情况 并输出成hprof文件
haha:分析hprof文件生成Snapshot对象 Snapshot用以查询最短引用链

流程图:

流程.png

1:RefWatcher负责的部分
2:HeapDumper 利用VM生成hprof文件
3:HeapAnalyzerService 分析hprof文件生成Snapshot对象继而得到最短引用链
4:DisplayLeakService 通知报警

关于Reference和ReferenceQueue

Reference

负责内存的状态
4个状态:

  • Active 内存一开始被分配的状态
  • Pending 快要被放进队列(将要被回收)
  • Enqueued 对象的内存已经被回收 此对象已经被放入ReferenceQueue中 后续可以根据此队列判断对象是否被回收
  • Inactive 最终状态 不能更改成其他状态

ReferenceQueue

检测到可达性更改后 垃圾回收机制会将已注册的引用对象添加到队列中 实现了入队(enqueue) 出队(poll) remove head(泛型的Reference)

如何检测一个对象是否被回收?
Reference + ReferenceQueue
步骤:
① 创建一个引用
② 创建Reference对象 并且关联
Reference被回收时会被添加到ReferenceQueue

ReferenceQueue queue = new ReferenceQueue();  //创建一个引用队列   
// 创建弱引用 此时状态为Active 并且Reference.pending为空 当前Reference.queue = 上面创建的queue 并且next=null  
WeakReference reference = new WeakReference(new Object(), queue);  // 将此Object注册到queue中
System.out.println(reference);  
// 当GC执行后 由于是弱引用 所以回收该object对象 并且置于pending上 此时reference的状态为PENDING  
System.gc();  
// ReferenceHandler从pending中取下该元素 并且将该元素放入到queue中 此时Reference状态为ENQUEUED Reference.queue = ReferenceENQUEUED  
// 当从queue里面取出该元素 则变为INACTIVE Reference.queue = Reference.NULL
Reference reference1 = queue.remove();  
System.out.println(reference1);

监听

onDestroy 中监听相应的对象是否被GC回收

检测

WeakReference创建时传入ReferenceQueueWeakReference引用的对象生命周期结束 当GC检测到会将它加入ReferenceQueue中 GC发生后 对象一直未被加入ReferenceQueue 那就可能存在内存泄漏 此时二次GC以确认

分析

利用haha获取内存中的堆信息 并生成Snapshot 继而查找强引用关系 确认最短强引用链

源码

监听

/**
 * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
 * references (on ICS+).
 */
public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class) //设置DisplayLeakService
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) //设置白名单
      .buildAndInstall();
}

/**
 * Builder to create a customized {@link RefWatcher} with appropriate Android defaults.
 */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context); //实例化了一个AndroidRefWatcherBuilder
}
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder {
    ...
    /**
    * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
    * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
    */
    public AndroidRefWatcherBuilder listenerServiceClass(
      Class listenerServiceClass) {
        return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
    }
    ...
}

public class RefWatcherBuilder> {
    ...
    public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
        this.heapDumpListener = heapDumpListener;
        return self();
    }
    ...
}
// 枚举定义白名单列表
public enum AndroidExcludedRefs {
    ACTIVITY_CLIENT_RECORD__NEXT_IDLE(VERSION.SDK_INT >= 19 && VERSION.SDK_INT <= 21) {
        void add(Builder excluded) {
            excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle").reason("Android AOSP sometimes keeps a reference to a destroyed activity as a nextIdle client record in the android.app.ActivityThread.mActivities map. Not sure what\'s going on there, input welcome.");
        }
    },
    SPAN_CONTROLLER(VERSION.SDK_INT <= 19) {
        void add(Builder excluded) {
            String reason = "Editor inserts a special span, which has a reference to the EditText. That span is a NoCopySpan, which makes sure it gets dropped when creating a new SpannableStringBuilder from a given CharSequence. TextView.onSaveInstanceState() does a copy of its mText before saving it in the bundle. Prior to KitKat, that copy was done using the SpannableString constructor, instead of SpannableStringBuilder. The SpannableString constructor does not drop NoCopySpan spans. So we end up with a saved state that holds a reference to the textview and therefore the entire view hierarchy & activity context. Fix: https://github.com/android/platform_frameworks_base/commit/af7dcdf35a37d7a7dbaad7d9869c1c91bce2272b . To fix this, you could override TextView.onSaveInstanceState(), and then use reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.";
            excluded.instanceField("android.widget.Editor$EasyEditSpanController", "this$0").reason(reason);
            excluded.instanceField("android.widget.Editor$SpanController", "this$0").reason(reason);
        }
    },
    ...
}

为什么忽略??

**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
        LeakCanary.enableDisplayLeakActivity(context);
        ActivityRefWatcher.install((Application) context, refWatcher);  //创建一个RefWatcher对象 用于检测对象是否被回收
    }
    return refWatcher;
}

内存分析是在另外一个进程(考虑分析可能会影响到主进程的性能) 如果判断当前的Application是在内存分析的进程启动则过滤不做初始化的操作

判断是否存在内存泄露

/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
    watch(watchedReference, "");
}

/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
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(); //生成一个随机数 用于判断对象是否被回收
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue); //申明一个弱引用 传入被观察对象
    ensureGoneAsync(watchStartNanoTime, reference);
}

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() { //保证分析是在主线程执行 延迟5秒分析
        @Override
        public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
        }
    });
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences(); //先将引用尝试从弱引用队列中poll出来 删retainedKeys中相对应的引用 retainedKeys中剩下没被回收的
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) { //检测是否已经被回收 不在retainedKeys中证明已经被回收
      return DONE;
    }
    gcTrigger.runGc(); //没有回收 手动调用GC
    removeWeaklyReachableReferences(); //再次尝试poll 检测是否被回收
    if (!gone(reference)) { //依旧没被回收 dump堆信息
        long startDumpHeap = System.nanoTime();
        long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY; //重试
        }
        long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
        heapdumpListener.analyze( //heapDumper.dumpHeap()生成.hprof 继而回调analyze
            new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
}
private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key); //每次GC后删除存入到队列中的引用(代表已经被回收)
    }
}

//判断是否给回收
private boolean gone(KeyedWeakReference reference) {
    return !this.retainedKeys.contains(reference.key);
}
public void runGc() {
    Runtime.getRuntime().gc();
    this.enqueueReferences();
    System.runFinalization(); //强制调用已经失去引用的对象的finalize方法
}

private void enqueueReferences() {
    try {
        Thread.sleep(100L);  //沉睡100毫秒 给GC时间
    } catch (InterruptedException var2) {
        throw new AssertionError();
    }
}
public void analyze(HeapDump heapDump) { //HeapDump是个model 储存一些分析类强引用路径的需要的信息
    Preconditions.checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
}
public static void runAnalysis(Context context, HeapDump heapDump, Class listenerServiceClass){
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra("listener_class_extra", listenerServiceClass.getName()); //IntentService
    intent.putExtra("heapdump_extra", heapDump);
    context.startService(intent);
}
protected void onHandleIntent(Intent intent) {
    if(intent == null) {
        CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.", new Object[0]);
    } else {
        String listenerClassName = intent.getStringExtra("listener_class_extra");
        HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
        HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); //找到引用关系
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); //启动一个服务
    }
}
//分析结果
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    if(!heapDumpFile.exists()) {
        IllegalArgumentException e1 = new IllegalArgumentException("File does not exist: " + heapDumpFile);
        return AnalysisResult.failure(e1, this.since(analysisStartNanoTime));
    } else {
        try {
            //以下类为haha库中类 
            MemoryMappedFileBuffer e = new MemoryMappedFileBuffer(heapDumpFile);
            HprofParser parser = new HprofParser(e); 
            Snapshot snapshot = parser.parse(); //获取内存堆快照
            this.deduplicateGcRoots(snapshot); //对快照去重 去除重复的强引用关系
            Instance leakingRef = this.findLeakingReference(referenceKey, snapshot); //拿到待分析的类 去快照找引用关系
            return leakingRef == null?AnalysisResult.noLeak(this.since(analysisStartNanoTime)):this.findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
        } catch (Throwable var9) {
            return AnalysisResult.failure(var9, this.since(analysisStartNanoTime));
        }
    }
}
public static void sendResultToListener(Context context, String listenerServiceClassName, HeapDump heapDump, AnalysisResult result) {
    Class listenerServiceClass;
    try {
        listenerServiceClass = Class.forName(listenerServiceClassName);
    } catch (ClassNotFoundException var6) {
        throw new RuntimeException(var6);
    }

    Intent intent = new Intent(context, listenerServiceClass);
    intent.putExtra("heap_dump_extra", heapDump);
    intent.putExtra("result_extra", result);
    context.startService(intent);
}
protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heap_dump_extra");
    AnalysisResult result = (AnalysisResult)intent.getSerializableExtra("result_extra");

    try {
        this.onHeapAnalyzed(heapDump, result);
    } finally {
        heapDump.heapDumpFile.delete();
    }

}
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", new Object[]{leakInfo});
    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if(shouldSaveResult) {
        heapDump = this.renameHeapdump(heapDump);
        resultSaved = this.saveResult(heapDump, result);
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    // 设置消息通知的 pendingIntent/contentTitle/contentText
    ...

    int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
    LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
    this.afterDefaultHandling(heapDump, result, leakInfo);
}

总结

  • 创建弱引用 关联监控的对象
  • 后台线程查看是否被清除 如果没有则调用GC
  • 如果引用未清除 dump堆内存 并且生成hprof文件
  • haha解析hprof文件 生成快照
  • 根据快照结合GC Root 得到最短强引用链 然后输出通知

原理
弱引用在没有引用链的情况下GC时会被加入ReferenceQueue中 遍历ReferenceQueue并从retainedKeys中删除相应的引用 后续根据retainedKeys中是否还存在此引用判断是否内存泄露 如果没有则证明此引用加入过ReferenceQueue中 那么可以证明其已经被回收或者将会被回收

你可能感兴趣的:(LeakCanary源码分析)