最近在做一个功能,涉及到了LeakCanary,因为使用比较简单,没有任何技术含量,就去了解了解原理。然后我的感受是,很神奇,很神奇。
准备分两部分来对其解说。第一部分是查找内存泄漏对应Reference的原理;第二部分是找到内存泄漏的Reference之后,如何查找对应的引用链。
附上LeakCanary源码
install()----->AndroidRefWathcherBuilder「构建者模式,构建观察者RefWatcher,buildAndInstall--(build RefWatcher 和对Activity和Fragment做install)」 -------->在ActivityRefWatcher中install做注册「Application的registerActivityLifecyclerCallBacks的 在监听中onActivityDestory,进行refWatcher.watch。」------>在watch中,用UUID做唯一key标识,将Reference添加到KeyedWeakReference,做软引用----->ensureGone
在Activity对象onDestory的时候,新见一个WeakReference对象指向Activity对象,如果Activity对象被垃圾回收的话,WeakReference对象就会进入引用序列的ReferenceQueue。所以我们只需要在Activity对象OnDestory之后去查看ReferenceQueue序列时候有该WeakReference对象即可。第一次观察是Activity的onDestory5秒后,如果发现ReferenceQueue对来还没有WeakReference对象,就进入第二次观察,如果有了,就证明没有泄漏,第二次观察跟第一次观察相比区别在于会先进行垃圾回收,在进行ReferenceQueue序列的观察。
在Java中,当对象o被创建的时候,是放在Heap中。当GC运行的时候,如果发现没有任何引用指向O,O就会被回收,腾出内存空间。一个对象被回收,必须满足两个条件。1)没有任何引用指向他,2)GC被运行
例如:
我们通常是把某个对象的reference设置为null来保证这个对象在下次GC的时候被回收。
ModelChapterContentReq req = new ModelChapterContentReq();
req = null;
但是,手动置空度对象对我们来说,真的是不太合适。对于简单的情况,不需要我们置空对象,
当调用他的方法执行完毕后,指向他的引用也会被从stack中popup,所以他就能在下一次的GC中回收了。但是也有特殊情况,在使用cache的时候,由于Cache的对象正是运行程序所需要的,那么只要程序正在运行,Cache中的引用就不会被GC(Cache中的引用拥有了有主程序一样的生命周期)。那么随着Cache中的引用越来越多,GC无法回收的O也越来越多,无法被自动回收, 当这些object需要被回收时, 回收这些object的任务只有交给程序编写者了. 然而这却违背了GC的本质(自动回收可以回收的objects).
这时候WeakReference出现了,当一个对象被WeakReference指向的时候,没有任何其引用指向的时候,如果GC,那这个对象就会被回收。
ModelChapterContentReq req = new ModelChapterContentReq();
WeakReference reference = new WeakReference(req);
WeakReference的一个特点是他何时被回收是不确定的,这是有GC的运行不确定的时候导致的,所以一般WeakReference引用的对象是有价值被Cache的,而且很容易被构建,且很消耗内存的对象。
ReferenceQueue:
在WeakReference在被回收之后,WeakReference其实本身也就没有用了,Java提供了一ReferenceQueue来保存已经被回收的Reference, 用法是在定义WeakReference的时候将一个ReferenceQueue的对象作为参数传入构造函数.
public void referenceTest(){
Test test = new Test();//test---强引用
ReferenceQueue queue = new ReferenceQueue();//引用序列
WeakReference weak = new WeakReference(test, queue);//弱引用
test = null;
Runtime.getRuntime().gc();//调用系统GC
System.runFinalization();//强制系统回收已经没有强引用的对象
Reference poll = null;
while ((poll = queue.poll()) != null) {
Log.i(TAG,"Reference"+poll.toString());
}
在这段代码中,把强引用test复制为null,则对象只有weak这个弱引用在。之后手动触发GC,日志中可以看到test的弱引用已经放到了引用队列中,说明Tes对象已经被回收。LeakCanary原理「1」利用这个原理初步定位内存泄漏对象,『2』在调用系统接口dump出堆转储文件快照.hprof,「3」调用haha库分析该文件解析出最短引用路径。
关于GC 补充的一个知识点:
对于JVM GC,里面涉及到的知识比较复杂。我就直接表达理论,你调用的GC,不一定会真正的去GC,尤其是在在Android5.0之后,你调用GC,没太大的作用。
public static void gc() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = justRanFinalization;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
就是在GC的前面做了很多处理,结果就是只有shouldRunGC等于true的时候,才会去进行JVM GC。
LeakCanary.install();
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
在refWatcher中,
参考掘金