LeakCanary详解
LeakCanary简介
leakCanary是square公司推出的一个用于检测内存泄漏的工具,在一个activity完全ondestroy方法执行时,我们都会希望它的内存空间能够完全被回收。但是实际上并非是这样的,往往可能会出现内存泄漏,比如说这个activity被其他没有回收的类所持有引用。那么就会造成ondestory方法执行完之后,这个activity没有被回收,造成内存泄漏。所以,为了让我们在开发测试时更容易的发现内存泄漏的情况,square公司推出了这个开源库。
LeakCanary流程
LeakCanary的使用
对于一个app引入leakCanary这个库是非常的简单的,只需要在gradle文件中引入下面几个依赖,这几个依赖会针对不同的编译版本引入不同的包:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
然后在代码中,我们只需要在我们项目中的application类去调用install方法建立LeakCanary监测就行了。
if (LeakCanary.isInAnalyzerProcess(this)) {
//这个是在分析heap的进程中,监测进程不能和分析进程在同一个。
return;
}
LeakCanary.install(this);
LeakCanary的简单实现流程
在我们在Application类中调用了LeakCanary的install方法之后,就开始整个activity的监测流程。
- 对application注册了一个关于activity生命周期的回调callBack,在这个回调里面,LeakCanary只关注onDestroy方法,毕竟我们需要监测内存泄漏,肯定是从onDestroy方法开始的。
- 一旦一个activity执行了ondestroy方法,也就是这个activity需要被销毁时,此时就开始了LeakCanary的工作,会在callBack里面执行watch方法,也就是开始监测了这个activity被销毁的过程。
- 在watch方法里面,会生成一个带有自定义key值的弱引用,LeakCanary通过这个弱引用来监测是否被完全回收,对于android里面的弱引用的回收,当弱引用指向的对象被gc回收时,此时会将这个弱引用添加到一个对应的队列里面。
- 假如watch监测出来可能出现内存溢出,此时就会利用一个叫做haha的库,这个库是回来解析heapDump文件的,然后通过这个文件分析是否真正的出现了内存溢出。出现溢出,生成溢出堆栈信息。
LeakCanary的代码实现:
RefWatcher的内部实现
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
在我们在application调用了install方法时,将会通过builder方法创建RefWatcher。
- DisplayLeakService是一个服务类,主要是用来分析泄漏的结果
- AndroidExcludedRefs排除android系统的bug 我们先看buildAndInstall的实现
public RefWatcher buildAndInstall() {
...
//创建RefWatcher实例
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (watchActivities) {
//开启Activity监听
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
//开启Fragment监听
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
在这个方法中,会创建出RefWatcher,同时开启Activity和Fragment的泄漏的监听。
首先,我们先看看如何开启Activity的监听。
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
其中,activityRefWatcher.lifecycleCallbacks为
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
所以,对于Activity的监听,其实就是对application注册了LifeCycleCallback,在每一个watcheActivity的时候,都会移除上一次的注册。
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
最后在onActivityDestroyed的回调,执行watch操作,进行内存泄漏的检测。
其实fragment和activity的监听其实是非常类似的,只不过在onDestory回调到时,调用了RefWatcher的watchFragments方法
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
在这个watchFragments中,注册了fragment的监听。
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
这个fragmentLifecycleCallbacks是fragment的lifeCycleCallback
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
这样,也就是完成了对于fragment的监听,在onFragmentDestroyed调用时,也会调用watch方法进行检测内存泄漏,只不过和activity不一样的是,在这里传入的fragment实例。
对于RefWatcher,这个类是LeakCanary的核心。我们可以先看看其中的一些成员变量
//执行内存泄漏的检测
private final WatchExecutor watchExecutor;
//用于查询是否在查询中
private final DebuggerControl debuggerControl;
//处理Gc,执行gc操作
private final GcTrigger gcTrigger;
//Dump内存泄漏的堆文件
private final HeapDumper heapDumper;
private final HeapDump.Listener heapdumpListener;
private final HeapDump.Builder heapDumpBuilder;
//产生内存泄漏的key
private final Set<String> retainedKeys;
//引用队列
private final ReferenceQueue<Object> queue;
在catch方法执行检测
//这个方法会在任意一个activity的ondestroy方法调用时调用
public void watch(Object watchedReference, String referenceName) {
//通过这个Disable参数可以仅在debug包中开启。
if (this == DISABLED) {
return;
}
//引用和引用名称不能为空
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
//记录监测的开始时间
final long watchStartNanoTime = System.nanoTime();
//生成一个随机的key,保证每次生成的弱引用的key都是不同的
String key = UUID.randomUUID().toString();
//添加到维护的key数组里面去,这是一个写时复制的数组,这个数组是用来记录LeakCanary生成的弱引用所对应的key,也就是记录监测的activity的key
retainedKeys.add(key);
//在这里生成弱引用,这个是LeakCanary自定义的,添加了key和引用名称,这个queue便是弱引用指向的对象被正确回收时会被添加到的队列。
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//开始监测这个引用的回收
ensureGoneAsync(watchStartNanoTime, reference);
}
上面的方法,除了最后一句话,其他的都是在做一些初始化操作而已,ensureGoneAsync这个方法才是执行的监测的入口,这个是一个同步方法,它会利用watchExecutor去顺序的执行每一个弱引用的回收确认:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//excutor监测池,持续监测这个弱引用是否被回收
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
通过了run方法的执行,开始通过ensureGone方法去确认每一个弱引用的回收,在阅读这个关键的方法时,我们需要先来了解两个方法removeWeaklyReachableReferences和isGone:
private void removeWeaklyReachableReferences() {
//这个会发生在finalization和垃圾收集器之前
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
=====>
public Reference<? extends T> poll() {
synchronized (lock) {
//queue的头为空,表示没有弱引用所指向的对象被回收
if (head == null)
return null;
return reallyPollLocked();
}
}
=====>
private Reference<? extends T> reallyPollLocked() {
if (head != null) {
//获取当前head头
Reference<? extends T> r = head;
//头等于尾,清空
if (head == tail) {
tail = null;
head = null;
} else {
获取下一个元素
head = head.queueNext;
}
//设置queueNext属性,标记一个引用有进队或者出队操作
r.queueNext = sQueueNextUnenqueued;
return r;
}
return null;
}
这个removeWeaklyReachableReferences方法会从前面指定的队列去获取是否存在被回收的弱引用,假如存在,则把我们保存的key数组中移除这个被成功回收的key值,剩下的就是可能存在泄漏的弱引用的key值,知道了这个,下面看看isGone的判断就比较简单了
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
只需要判断retainedKeys数组中是否包含该引用的key就能知道有没有被回收了。下面来看看这这个ensureGone方法,看看是如何确认有没有造成内存泄漏的
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//通过回收队列的引用去移除我们的key数组
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
return RETRY;
}
//如果被标记的弱引用均已被回收,那么返回。
if (gone(reference)) {
return DONE;
}
//在这里进行二次确认,手动执行一次gc
gcTrigger.runGc();
//在gc完成之后,再此通过queue移除我们的key数组。
removeWeaklyReachableReferences();
if (!gone(reference)) {
//仍然存在没被回收的弱引用,此时可能造成内存泄漏,我们需要通过heapDump文件来分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//根据现在内存,生成dump文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
//目前不能读取,待重试状态
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//heapdumpListener分析这个heapDump文件,判断是否泄漏。
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
当有可能造成内存泄漏的时候,会通过heap内存文件分析是否真正的出现了内存泄漏,会将这个文件的分析转交给分析类去处理,而Watcher在此时就完成了自己的工作。
所以,watcher类主要做的事情就是在每一个ondestory调用时定义一个相对应的弱引用,并在gc后分析queue和key数组,判断是否可能造成泄漏,在有可能造成泄漏的时候,将dump文件的分析转交给其他类去处理。
Dump
我们看看dump文件分析的主要代码,首先watcher会通过listener将dump文件转交给监听,下面是这个listener的调用链:
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
public void analyze(HeapDump heapDump) {
Preconditions.checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
}
HeapAnalyzeService是一个IntentService,它有自己的工作线程,在这个工作线程执行完毕之后,会关闭自身service。
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);
}
工作线程的执行,
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");
//将dump文件的分析交给了HeapAnalyzer类
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
//获取泄漏的结果。
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
通过着listener会开启一个IntentService,intentService与普通的service最主要的区别就是拥有自己的工作线程,而且待工作线程执行完毕之后,会自己关闭service,不用我们去手动进行控制。在HeapAnalyzerService中,又通过HeapAnalyzer去分析弱引用。我们来看看checkForLeak这个方法:
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
...
//heap文件是.hprof后缀格式的
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//获取解析器,这个解析器库haha所提供的
HprofParser parser = new HprofParser(buffer);
//生成此时的内存快照
Snapshot snapshot = parser.parse();
//复制gc的根对象,这个root是用来判断可达性的,从root到我们的弱引用所指向的对象,
deduplicateGcRoots(snapshot);
//寻找泄漏的引用
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
}
首先会生成内存快照,然后通过此时的快照来获取所有的根节点,通过这个根节点来判断弱引用指向对象的可达性。我们看看寻找泄漏引用的逻辑:
private Instance findLeakingReference(String key, Snapshot snapshot) {
//通过自定义弱引用对象的name获取该对象
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List<String> keysFound = new ArrayList<>();
//获取当前的实例列表
for (Instance instance : refClass.getInstancesList()) {
//获取实例的参数域
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
//获取key值
String keyCandidate = asString(fieldValue(values, "key"));
if (keyCandidate.equals(key)) {
//如果需要寻找的key和获取出来的对象的key值一致,表示寻找到了这个弱引用,即有泄漏。
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
如果返回的不为空,那么返回寻找到的泄漏的弱引用对象。