在Android开发中最让人们头疼的就是内存泄漏了,今天来介绍一个查看内存是否泄漏的工具LeakCanary,并通过研究源码明白它是如何分析和查找存在泄漏信息的
首先送上LeakCanary文档链接:LeakCanary中文使用说明
Part1. 知识回顾
常用工具
-
- Mat
-
- LeakCanary(Square)
原理:watch监视一个即将要销毁的对象
- LeakCanary(Square)
内存种类
- 1、栈(stack-基本数据类型,对象的引用)
- 2、堆(heap-存放new出来的对象和数组,在堆中分配内存由GC管理)
- 3、方法区(method,大体和堆一样)
为什么会产生内存泄漏
- 当一个对象已经不需要再使用了,在该对象被回收时候,有另外的对象引用该回收对象,导致本该被回收的对象无法回收
- 有些对象只有有限的生命周期,当生命周期很短的完成任务后,在本该结束的生命周期中仍然被引用
内存泄漏会导致什么问题
- OOM
常见的内存泄漏情况
- 单例造成的内存泄漏
- 非静态内部类创建静态实例造成的内存泄漏
- handler造成内存泄漏(handler、message、MessageQueue)
解决方法:
①将Handler声明为静态类型
②通过弱引用的方式引入Activity - 线程造成的内存泄漏(解决方法:将线程定义声明为static类型)
- webview造成的内存泄漏(example:加载页面很复杂,Ex:大量的图片)
Part2 概念
引用类型
- 强引用(StrongReference),默认对象一般为强引用
- 软引用(SoftReference),当内存空间足够大时相当于强引用,内存不够时通过垃圾回收器(GC)自动回收
- 弱引用(WeakReference),当GC扫描到该类型的引用时就自己回收
- 虚引用,相当于没有进行引用,GC可随时回收该类型的引用
ReferenceQueue
- 软引用和弱引用都持有该对象
- 对象被垃圾回收,Java虚拟机就会把这个引用加入到与之相关联的引用队列中
Part3.LeakCanary使用
- 在module层级中的build.gradle中加入引用,不同的编译使用不同的引用
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
2.在Application中:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
3.在Manifest.xml中加载该Application文件
Part4. LeakCanary源码剖析
从代码入口剖析:
LeakCanary.install(this);
跟踪源码可知
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return install(application, DisplayLeakService.class);
}
从上面的代码我们发现这个方法最终返回给我们一个RefWatcher这个类,这个类是主要是启动ActivityRefWatcher类,ActivityRefWatcher在Activity的onDestory方法结束时检测内存泄漏。
看下install这个方法:
/**
* Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
* activity references (on ICS+).
*/
public static RefWatcher install(Application application,
Class extends AbstractAnalysisResultService> listenerServiceClass) {
if (isInAnalyzerProcess(application)) {
return RefWatcher.DISABLED;
}
enableDisplayLeakActivity(application);
HeapDump.Listener heapDumpListener =
new ServiceHeapDumpListener(application, listenerServiceClass);
RefWatcher refWatcher = androidWatcher(application, heapDumpListener);
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}
通过RefWatcher refWatcher = androidWatcher(application, heapDumpListener)
创建一个RefWatcher对象,启动activityRefWatcher来监视内存泄漏
enableDisplayLeakActivity(application)
主要作用是开启DisplayLeakActivity这个类,这个类主要是显示内存泄漏的弹框页面
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
小结:
①通过stopWatcher方法反注册以前的Activity的生命周期的callback,目的是为了保证以前的内存泄漏的activity删除
②重新注册activity生命周期的callback
③通过lifecycleCallbacks中的onActivityDestroyed方法将activity的生命周期和ActivityReference关联起来
@Override
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
跟踪onActivityDestroyed方法发现通过调用RefWatcher调用了watcher类
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
private final RefWatcher refWatcher
下面我们进入RefWatcher类中发现有如下的变量信息
private final Executor watchExecutor;
private final DebuggerControl debuggerControl;
private final GcTrigger gcTrigger;
private final HeapDumper heapDumper;
private final Set retainedKeys;
private final ReferenceQueue
上述变量大意如下:
- watchExecutor主要用于执行内存泄漏检测
- debuggerControl查询我们是否正在调试中,如果我们正在调试过程中则不会进行判断
- gcTrigger用于处理GC,用于在判断泄漏对象之前再调用GC类中的方法再次判断
- heapDumper用于dump中内存泄漏堆文件
- retainedKeys该set集合持有待检测和已产生内存泄漏信息的key
- queue引用对象,主要是判断弱引用所持有的对象是否已执行GC垃圾收回
- heapdumpListener主要用于分析产生hprof文件回调
查看watch方法可知:
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
if (debuggerControl.isDebuggerAttached()) {
return;
}
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() {
@Override public void run() {
ensureGone(reference, watchStartNanoTime);
}
});
}
通过产生一个唯一的key添加到retainedKeys
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
再创建一个KeyedWeakReference的弱引用,并开启一个异步线程来分析创建好的弱引用,该线程主要作用是确保我们的Activity是否真正已经进入到GONE状态
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
//计算过去的方法到调用GC垃圾收回的时间值
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//清除已经到达我们引用队列的弱引用
removeWeaklyReachableReferences();
//判断如果处于debug状态就不再进行内存分析
if (gone(reference) || debuggerControl.isDebuggerAttached()) {
return;
}
gcTrigger.runGc();//手动进行垃圾回收
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();//dump出内存泄漏的文件
if (heapDumpFile == null) {
// Could not dump the heap, abort.
return;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//开始分析内存泄漏文件查找内存泄漏路径
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
heapDumpDurationMs));
}
}
以上代码部分总结如下:
- 首先创建一个RefWatcher,启动一个ActivityRefWatcher
- 通过ActivityLifecyclecallback将Activity的onDestroy生命周期给关联起来
- 最后通过执行execute线程来分析泄漏信息
探讨LeakCanary中Activity泄漏检测机制代码
在上面的ensureGone方法中最后我们发现有这样的代码
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
heapDumpDurationMs));
}
通过跟踪发现analyze方法该方法是HeapDump
类中的一个interface接口,再查看它的实现类发现在ServiceHeapDumpListener
这个类中的方法
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
继续跟踪runAnalysis
方法发现在HeapAnalyzerService
中,且该类继承了intentService
,因此它将会每次调用onHandleIntent
方法
@Override
protected void onHandleIntent(Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
通过checkForLeak方法来分析内存泄漏信息的结果,并通过sendResultToListener显示最终的结果
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
ISnapshot snapshot = null;
try {
snapshot = openSnapshot(heapDumpFile);//生成内存快照信息
IObject leakingRef = findLeakingReference(referenceKey, snapshot);//查看内存的引用
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
String className = leakingRef.getClazz().getName();
AnalysisResult result =
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true);
if (!result.leakFound) {
result = findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, false);//寻找内存泄漏的路径
}
return result;
} catch (SnapshotException e) {
return failure(e, since(analysisStartNanoTime));
} finally {
cleanup(heapDumpFile, snapshot);
}
总结checkForLeak方法
1.把.hprof转为 Snapshot
snapshot = openSnapshot(heapDumpFile);
2.找出泄漏的对象/泄漏对象的最短路径
IObject leakingRef = findLeakingReference(referenceKey, snapshot);
AnalysisResult result =
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true);
findLeakingReference作用
①在snapshot快照中找到第一个弱引用即为内存发生泄漏的引用
②遍历这个对象的所有实例信息
③如果发现存在key值与之前定义封装好的key值相同,那么返回这个定位到的泄漏对象
findLeakTrace是通过获取内存泄漏的引用来获取泄漏路径的最短路径
了解LeakCanary的原理
- Activity Destroy()之后将它放在一个WeakReference中
- 将WeakReference关联到一个ReferenceQueue
- 查看ReferenceQueue是否存有Activity的引用
- 如果该Activity泄露了,Dump出heap信息,然后去分析泄漏路径