前言
leakcanary 的官方网址:https://square.github.io/leakcanary/
LeakCanary使用
引用
升级到2.0之后,使用起来非常简单,只需要在build.gradle中添加依赖
注意这里不要直接使用implementation,使用implementation虽然不会在正式版中弹出内存泄漏的弹窗(因为LeakCanary内部做了限制),但是会增大约1mb的安装包体积
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
在自定义buildtype中设置debuggable true,并设置测试版本的签名文件
debug{
debuggable true
...
}
模拟内存泄漏
*(1) 新建一个单例类
public class SingleInstance {
private Context context;
private SingleInstance(Context context){
this.context = context;
}
public static class Hodle {
private static SingleInstance singleInstance;
public static SingleInstance newInstance(Context context){
if(singleInstance == null){
singleInstance = new SingleInstance(context);
}
return singleInstance;
}
}
}
- (2)SecondActivity的引用加入到单例中
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
SingleInstance.Holder.newInstance(this);
}
}
-
(3)当按back返回键时,SecondActivity会被销毁,但是引用还被单例持有,这时候SecondActivity 会出现内存泄漏。
-
LeakCanary 监测到内存泄漏会发送一个通知,点开通知可以看到详细的内存泄露堆栈信息。
-
同样也可以在Logcat中看到。
LeakCanary 原理
1.0 原理
在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
随后,会向主线程的MessageQueue添加一个IdleHandler,用于在idle时触发一个发生在HandlerThread的等待5秒后开始检测内存泄漏的代码。
这段代码首先会判断是否对象出现在引用队列中,如果有,则说明没有内存泄漏,结束。否则,调用Runtime.getRuntime().gc()进行GC,等待100ms后再次判断是否已经出现在引用队列中,若还没有被出现,那么说明有内存泄漏,开始dump hprof
2.0 原理
在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。
这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用Runtime.getRuntime().gc()进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。
-
思考1:如何判断一个对象是否被回收
如果一个对象除了弱引用以外,没有被其他对象所引用,当发生GC时,这个弱引用对象就会被回收,并且被回收掉的对象会被存放到ReferenceQueue中,所以当ReferenceQueue中有这个对象就代表这个对象已经被回收,反之就是没有被回收 -
思考2: 这里为什么要延迟五秒执行任务
我们都知道GC不是即时的, 页面销毁后预留5秒的时间给GC操作, 再后续分析引用泄露, 避免无效的分析
安装注册原理
-
Activity
ActivityDestroyWatcher.install(application, objectWatcher, configProvider) -
Fragment
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
通过向Application注册registerActivityLifecycleCallbacks从而获取每个启动的Activity,然后
对于每个Activity,在其onDestroy方法调用之后,调用objectWatcher.watch观察这个Activity
对于Fragment而言,由于Fragment需要依附于Activity,且需要从Activity中获取FragmentManager,然后通过其registerFragmentLifecycleCallbacks方法观察Fragment,这样在Fragment调用onDestroyView和onDestory之后就能观察Fragment的View或者Fragment本身。
内存泄漏判定
ObjectWatcher.watch(Any) ,Activity、Fragment的View、Fragment都是由该方法进行观察的。
/**
* Identical to [watch] with an empty string reference name.
*/
@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}
/**
* Watches the provided [watchedObject].
*
* @param name A logical identifier for the watched object.
*/
@Synchronized fun watch(
watchedObject: Any,
name: String
) {
if (!isEnabled()) {
return
}
// 将ReferenceQueue中出现的弱引用移除
// 这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
// 记下观测开始的时间
val watchUptimeMillis = clock.uptimeMillis()
// 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器
// 这样的话,弱引用被回收之前会出现在ReferenceQueue中
val reference =
KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (name.isNotEmpty()) " named $name" else "") +
" with key $key"
}
// 将key-reference保存到map中
watchedObjects[key] = reference
// 主线程5秒之后执行moveToRetained(key)方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
LeakCanary的关键代码之一: (1)将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。 (2) 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。
为什么会是5S,这里猜测与Android GC有关。在Activity.H中,收到GC_WHEN_IDLE消息时会进行Looper.myQueue().addIdleHandler(mGcIdler),而mGcIdler最后会触发doGcIfNeeded操作,在该方法中会判断上次GC与现在时间的差值,而这个值就是MIN_TIME_BETWEEN_GCS = 5*1000。
弱引用入队列发生在终结函数或者GC发生之前。
- removeWeaklyReachableObjects()
将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏。
private fun removeWeaklyReachableObjects() {
// 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.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
- moveToRetained
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
5秒钟到了,还是先将引用队列中出现的对象从map中移除,因为它们没有内存泄漏。然后判断key还在不在map中,如果在的话,说明可能发生了内存泄漏。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis字段,然后通知所有的对象,内存泄漏发生了。
hprof文件生成与解析
解析hprof文件,LeakCanary2使用的是Shark