LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。

Java基础知识

在总结之前需要先了解一些Java的基础知识。

1. 强引用

通常可以认为是通过new出来的对象,即使内存不足,GC进行垃圾收集的时候也不会主动回收。
Object obj = new Object();
2. 软引用

在内存不足的时候,GC进行垃圾收集的时候会被GC回收。
Object obj = new Object(); SoftReference<Object> softReference = new
SoftReference<>(obj);
3. 弱引用

无论内存是否充足,GC进行垃圾收集的时候都会回收。
Object obj = new Object(); WeakReference<Object> weakReference = new
WeakReference<>(obj);
4. 虚引用

和弱引用类似,主要区别在于虚引用必须和引用队列一起使用。
Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new
ReferenceQueue<>(); PhantomReference<Object> phantomReference = new
PhantomReference<>(obj, referenceQueue);
5. 引用队列

如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。

6. GC回收机制算法 - 可达性算法

可达性算法是目前Java虚拟机比较常用的GC回收机制算法。

主要原理是是通过GCRoots对象作为根节点,从根节点开始往下遍历,遍历的路径称为引用链,当一个对象到GC
Roots没有任何引用链相连的时候,则说明此对象是不可用的。

LeakCanary 集成和使用

首先需要配置build.gradle
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
然后在Application类中加入以下代码。
public class DevApp extends Application { @Override public void onCreate() {
if (!LeakCanary.isInAnalyzerProcess(this)) { LeakCanary.install(this); }
super.onCreate(); } }

LeakCanary默认监控Activity类,如果我们需要自定义监控对象,那么就需要在我们确定不需要某个对象之后,手动调用RefWatcher对象的watch方法进行监控。
public class DevApp extends Application { @Override public void onCreate() {
LeakCanaryWatcher.initialize(this); super.onCreate(); } } public class
LeakCanaryWatcher { private static RefWatcher sRefWatcher; private
LeakCanaryWatcher() { throw new IllegalStateException("Utility class"); }
public static void initialize(Application application) { if
(!LeakCanary.isInAnalyzerProcess(application)) { sRefWatcher =
LeakCanary.install(application); } } public static void watch(Object obj) { if
(sRefWatcher != null) { sRefWatcher.watch(obj); } } }
LeakCanary原理

首先从install方法入手,分析LeakCanary是如何监控内存泄漏的。
public static RefWatcher install(Application application) { return
refWatcher(application) .listenerServiceClass(DisplayLeakService.class)
.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); }
可以看出LeakCanary使用了构建者模式去创建RefWatcher对象(实际上很多开源框架都使用了构建者模式去创建对象)。

listenerServiceClass方法是用来显示内存分析结果的,和原理联系不大。

excludedRefs则是排除了一些系统造成的内存泄漏,提高了检测精度。

下面我们分析buildAndInstall方法
public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if
(refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher); } return
refWatcher; } public static void install(Application application, RefWatcher
refWatcher) { new ActivityRefWatcher(application,
refWatcher).watchActivities(); } public void watchActivities() { // Make sure
you don't get installed twice. stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } private
final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new
Application.ActivityLifecycleCallbacks() { @Override public void
onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override
public void onActivityStarted(Activity activity) { } @Override public void
onActivityResumed(Activity activity) { } @Override public void
onActivityPaused(Activity activity) { } @Override public void
onActivityStopped(Activity activity) { } @Override public void
onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity); } }; void
onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }

代码很清晰,build方法很简单,就是对一些参数的配置,重点在install方法。向Application注册一个Activity的生命周期回调,并且在onActivityDestroyed回调中调用watch方法进行监控,这也是之前提到的LeakCananry框架默认监控Activity的原因。

LeakCanary核心的代码在watch方法中
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); }

retainedKeys是一个Set集合,queue就是之前提到的引用队列。这里使用UUID为监控对象生成了一个唯一的key,并将key放入Set集合,同时创建了一个监控对象的弱引用。

准备工作做完了,下面开始对对象进行内存泄漏分析。
private void ensureGoneAsync(final long watchStartNanoTime, final
KeyedWeakReference reference) { watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() { return ensureGone(reference,
watchStartNanoTime); } }); } Retryable.Result ensureGone(final
KeyedWeakReference reference, final long watchStartNanoTime) { .....
removeWeaklyReachableReferences(); // 如果是debug模式,不进行分析 if
(debuggerControl.isDebuggerAttached()) { // The debugger can create false
leaks. return RETRY; } // 如果Set集合里不包含监控对象,那么说明已经分析过了,不需要再次分析 if
(gone(reference)) { return DONE; } // 手动触发GC gcTrigger.runGc();
removeWeaklyReachableReferences(); // 如果Set集合仍然包含监控对象,说明监控对象发生了内存泄漏 if
(!gone(reference)) { ..... } 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); } } private boolean gone(KeyedWeakReference
reference) { return !retainedKeys.contains(reference.key); }
启用了一个子线程去监控内存泄漏。


基本方法是手动触发GC,然后将引用队列里的元素出队列(GC后存在于引用队列说明被回收),从Set集合中将出队列的元素的key移除,如果移除后Set集合然后包含某对象的key值,说明该对象没有被回收,即发生了内存泄漏。

总结

1. 使用UUID为监控对象创建一个唯一的key值,并将key放入Set集合中。

2. 创建一个监控对象的弱引用,并开启线程进行内存分析。

3 手动触发GC,利用弱引用被回收会被放入引用队列的机制,将引用队列里的被回收对象出列,并从Set集合中移除对应的key值。

4 如果Set集合中监控对象的key值没有被移除,说明监控对象没有被放入引用队列,即没有被回收,发生了内存泄漏。

 

 

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信