核心是用RefWatcher在生命周期结束时实现监测,通过JVM弱引用和ReferenceQueue绑定的原理来捕获内存泄露事件,再通过HAHA开源库去分析引用路径,基本步骤包括:
1.监听生命周期
通过Application的registerActivityLifecycleCallbacks方法,抓到Activity的ondestroy生命周期,把Activity丢给RefWatcher来监测。
对于Fragment,就需要在Fragment中手动编写,先从Application中实现RefWatcher,然后在Fragment的onDestroy方法中用refWatcher.watch(this)监控。
2.工作线程监测对象清除
启动
内存泄露检查其实比较耗时,为尽量避免干扰,LeakCanary会在主线程空闲时启动泄露检查,主要是通过在Looper中add一个IdleHandler(在messagequeue的next函数中判断,待处理msg为空,或者没有需要延迟执行的msg,这时会处理所有的IdleHandler)来实现的(并且会延迟5秒)。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
检查
在refWatcher.watch函数中,Activity被包装为一个KeyedWeakReference对象,KeyedWeakReference会在工作线程中监测引用是否被清除。
KeyedWeakReference有key和referenceQueue。
其中key是一个UID,用来定位最终泄露的对象,在retainedKeys(一个CopyOnWriteArraySet)里会add这个key,如果retainedKeys里不再有对应的key,就说明对象已经被回收了;
referenceQueue用来监控弱引用的回收,这里应用了JVM中回收时虚引用和ReferenceQueue关联的原理,当GC回收一个对象时,如果发现它还有弱引用,就会在回收对象内存前,把这个对象加入到与弱引用关联的ReferenceQueue中,所以,如果一个对象出现在了相关联的ReferenceQueue中,就说明它即将被GC回收。
所以,检查对象是否被清除,就通过相关联的referenceQueue做检查,如果queue中能pull出这个对象,就说明可以回收,清除掉retainedKeys里的key记录即可
//给对象关联ReferenceQueue,以便判断回收
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
...
while ((ref = (KeyedWeakReference) queue.poll()) != null) {//检查关联的referenceQueue
retainedKeys.remove(ref.key);//清除key,只要没有这个key,就说明已清除
}
如果未清除,手动调用GC,如果还未清除,就有可能是内存泄露了,把内存dump到.hprof文件,用HeapAnalyzer去分析
3.分析内存泄露
在精细分析中实际上引用了开源项目HAHA。
先把.hprof转成Snapshot,拿到对象到GCRoot的所有引用路径,并剔除重复路径
再找到泄露对象:
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());//找到泄露的类
for (Instance instance : refClass.getInstancesList()) {...//遍历类的实例
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
String keyCandidate = asString(fieldValue(values, "key"));//根据KeyedWeakReference对象中的key值,找到泄露对象
最后找出最短引用路径:
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//拿最短引用路径
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);//生成LeakTrace
4.输出
最开始初始化,LeakCanary.intall(application)时,实际上注册了处理方式:
public static RefWatcher install(Application application) {
return refWatcher(application)
.listenerServiceClass(DisplayLeakService.class)//处理方法
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())//排除部分泄露范围
.buildAndInstall();
}
所以,是在DisplayLeakService.class中处理泄露的,我们可以自己扩展,比如改成发邮件什么的。
5.其他
为了确保上线安全,通过设定debugCompile,实现仅在debug中使用LeakCanary。
引用
Leakcanary
LeakCanary核心原理源码浅析
Java对象的强、软、弱和虚引用原理+结合ReferenceQueue对象构造Java对象的高速缓存器
网友评论