LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。
在总结之前需要先了解一些Java的基础知识。
通常可以认为是通过new出来的对象,即使内存不足,GC进行垃圾收集的时候也不会主动回收。
Object obj = new Object();
在内存不足的时候,GC进行垃圾收集的时候会被GC回收。
Object obj = new Object();
SoftReference
无论内存是否充足,GC进行垃圾收集的时候都会回收。
Object obj = new Object();
WeakReference
和弱引用类似,主要区别在于虚引用必须和引用队列一起使用。
Object obj = new Object();
ReferenceQueue
如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。
可达性算法是目前Java虚拟机比较常用的GC回收机制算法。
主要原理是是通过GCRoots对象作为根节点,从根节点开始往下遍历,遍历的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则说明此对象是不可用的。
首先需要配置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);
}
}
}
首先从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值没有被移除,说明监控对象没有被放入引用队列,即没有被回收,发生了内存泄漏。