参考文档
http://blog.csdn.net/wyfei021/article/details/46506521
http://vjson.com/wordpress/leakcanary%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e7%ac%ac%e4%b8%89%e8%ae%b2%ef%bc%8dheapanalyzerservice%e8%af%a6%e8%a7%a3.html
http://blog.csdn.net/u011291205/article/details/52497078
http://www.jianshu.com/p/481775d198f0
http://www.jianshu.com/p/5ee6b471970e
http://blog.csdn.net/cloud_huan/article/details/53081120
http://vjson.com/wordpress/leakcanary%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e7%ac%ac%e4%b8%80%e8%ae%b2.html
http://www.cnblogs.com/xgjblog/p/6084388.html
leakcanary 内存泄露监测工具
https://github.com/square/leakcanary
源码流程分析
官方demo
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity); View button = findViewById(R.id.async_task);
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
startAsyncTask();
}
});
} void startAsyncTask() {
//内存泄漏原因分析 匿名类隐式持有外部类MainActivity的引用 所以假如任务完成前Activity
//销毁了(例如转屏)则activity对象会发生内存泄漏
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
//后天做一些耗时任务
SystemClock.sleep(20000);
return null;
}
}.execute();
}
}
public class ExampleApplication extends Application { @Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
enabledStrictMode();
LeakCanary.install(this);
} private static void enabledStrictMode() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}
}
开始ExampleApplication
LeakCanary.install(this);
LeakCanary
install静态方法里面包含了3个方法,这3个方法都是通过AndroidRefWatcherBuilder这个辅助类来配置相关信息的
listenerServiceClass方法是和结果分析相关的服务绑定,绑定到DisplayLeakService.class这个类上面,这个类负责通知泄漏消息给你
excludedRefs方法是排除一些开发可以忽略的泄漏路径(一般是系统级别BUG),这些枚举在AndroidExcludedRefs这个类当中定义
buildAndInstall这才是重点方法
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
AndroidRefWatcherBuilder
实例化RefWatcher对象,这个对象是用来判断泄漏对象的
AndroidRefWatcherBuilder
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
ActivityRefWatcher 核心方法找到了,就是registerActivityLifecycleCallbacks这个方法,这是application提供的一个方法,用来统一管理所有activity的生命周期,LeakCanay是在onDestory()方法实现监控的
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);
}
之后把activity直接扔给RefWatcher类处理,所以第一部分完成
第二部分 RefWatcher
上面做的事情就是把activity对象封装成带key值和带引用队列(ReferenceQueue)的KeyedWeakReference对象,key值是用来最终定位泄漏对象用的,
第三部分会用到,引用队列是用来监控弱引用回收的,这个类是继承WeakReference类,所以封装完你会看到这个类的一些特点:
1.带key,通过UUID.randomUUID().toString(),是唯一key序列
2.包装成WeakReference并添加到ReferenceQueue
public void watch(Object watchedReference) {
watch(watchedReference, "");
} 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);
}
触发watch动作过程分析:当Activity的onDestroy调用的时候,Application会收到通知,然后调用
lifecycleCallback.onActivityDestroyed()方法,最终RefWatcher的watch方法被触发,也就实现
了Activity内存泄漏分析自动分析。
继续走 进入核心代码
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) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除弱引用
removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) {
//如果VM正连接到Debuger,忽略这次检测,因为Debugger可能会持有一些在当前上下文中不可见的对象,导致误判
return RETRY;
}
if (gone(reference)) {//如果引用已经不存在了则返回
return DONE;
}
gcTrigger.runGc();//触发GC
removeWeaklyReachableReferences();//再次移除弱引用,二次确认
if (!gone(reference)) {//如果GC之后引用还是存在,那么就进行深入分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap();//dump内存
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(//分析Hprof文件
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
如果这个对象作为弱引用,被回收了,那么添加到引用队列(ReferenceQueue)当中去,所以这个函数.poll是出栈的意思,
如果成功出栈了,那么说明你加入了引用队列,然后可以认为是已经被回收了的,然后retainedKeys这个是一个Set容器,
在之前会加入生成的唯一key作为标识,这里如果这个对象回收了,那么就移除这个key值。
然后是gone函数,就是看retainedKeys容器有没有key,如果回收了,就不存在key了,那么就没有泄漏,否则就怀疑有泄漏。
然后后面的手动GC和检查都是一个类似二次确认的道理,还是没有回收,那么才会进入精确阶段,.hropf分析大法,这是第三部分内容。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
} 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);
}
}
内存快照分析
那么在第二步根据弱引用有没有回收这个上已经是基本确定了这个对象有没有泄漏,
那么下一部就是获取dumpheap以及对这个dumpheap进行analyze。
调用就是下面这几行,然而这几行的背后其实引用的又是另外一个项目haha,具体的开源地址为:https://github.com/square/haha
这边只是接口
public interface Listener {
Listener NONE = new Listener() {
@Override public void analyze(HeapDump heapDump) {
}
}; void analyze(HeapDump heapDump);
}
具体实现是在 ServiceHeapDumpListener
@Override
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
继续封装 HeapAnalyzerService
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
} @Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA); HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs); AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
checkForLeak就是最为关键的方法 进入HeapAnalyzer
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));
} try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);//解析器解析文件
Snapshot snapshot = parser.parse();//解析过程,是基于google的perflib库,根据hprof的格式进行解析
deduplicateGcRoots(snapshot);//分析结果进行去重 //此方法就是根据我们需要检测的类的key,查询解析结果中是否有我们的对象,获取解析结果中我们检测的对象
Instance leakingRef = findLeakingReference(referenceKey, snapshot); //此对象不存在表示已经被gc清除了,不存在泄露因此返回无泄漏
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
//此对象存在也不能也不能确认它内存泄漏了,要检测此对象的gc root
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
上面的checkForLeak方法就是输入.hprof,输出分析结果,主要有以下几个步骤:
1.把.hprof转为Snapshot,这个Snapshot对象就包含了对象引用的所有路径
2.精简gcroots,把重复的路径删除,重新封装成不重复的路径的容器
3.找出泄漏的对象
4.找出泄漏对象的最短路径
private Instance findLeakingReference(String key, Snapshot snapshot) {
//因为需要检测的类都构造了一个KeyedWeakReference,因此先找到KeyedWeakReference,就可以找到我们的对象
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List<String> keysFound = new ArrayList<>();
//循环所有KeyedWeakReference实例
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
//找到KeyedWeakReference里面的key值,此值在我们前面传入的对象唯一标示
String keyCandidate = asString(fieldValue(values, "key"));
if (keyCandidate.equals(key)) { //当key值相等时就表示是我们的检测对象
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
那么上面这个方法是在snapshot快照中找到第一个弱引用(因为就是这个对象没有回收,泄漏了嘛),然后根据遍历这个对象的所有实例,
如果key值和最开始定义封装的key值相同,那么返回这个泄漏对象,就是已近在快照中定位到了泄漏对象了。
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
//这两行代码是判断内存泄露的关键,我们在上篇中分析hprof文件,判断内存泄漏
//判断的依据是展开调用到gc root,所谓gc root,就是不能被gc回收的对象,
//gc root有很多类型,我们只要关注两种类型1.此对象是静态 2.此对象被其他线程使用,并且其他线程正在运行,没有结束
//pathFinder.findPath方法中也就是判断这两种情况
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // 找不到引起内存泄漏的gc root,就表示此对象未泄漏
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
//生成泄漏的调用栈,为了在通知栏中显示
LeakTrace leakTrace = buildLeakTrace(result.leakingNode); String className = leakingRef.getClassObj().getClassName(); // Side effect: computes retained size.
snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance;
//计算泄漏的空间大小
long retainedSize = leakingInstance.getTotalRetainedSize(); // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
} return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
最后一个步骤是根据上一部得到的泄漏对象找到最短路径,封装在ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);这句中、
总结:在第三个分析步骤,解析hprof文件中,是先把这个文件封装成snapshot,然后根据弱引用和前面定义的key值,确定泄漏的对象,最后找到最短泄漏路径,作为结果反馈出来,
那么如果在快照中找不到这个怀疑泄漏的对象,那么就认为这个对象其实并没有泄漏,因为已经回收了
总结
LeakCanay的入口是在application的onCreate()方法中声明的,其实用的就是Application的ActivityLifecycleCallbacks回调接口监听所有activity的onDestory()的,
在这个方法进行RefWatcher.watch对这个对象进行监控。
具体是这样做的,封装成带key的弱引用对象,然后GC看弱引用对象有没有回收,没有回收的话就怀疑是泄漏了,需要二次确认。然后生成HPROF文件
源码API分析
首先分析系统API
Application ActivityLifecycleCallbacks
作用 Application通过此接口提供了一套回调方法,用于让开发者对Activity的生命周期事件进行集中处理。仅限4.0以后的版本使用
public class BaseApplication extends Application { @Override
public void onCreate() {
super.onCreate(); //Application下的每个Activity声明周期改变时,都会触发以下的函数
registerActivityLifecycleCallbacks(new 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) {
Toast.makeText(getBaseContext(), "onActivityDestroyed", Toast.LENGTH_SHORT).show();
}
});
}
}
System.nanoTime()
作用 提供相对精确的计时 给一些性能测试提供了更准确的参考
与System.currentTime()区别 System.currentTime()的精度是毫秒 返回值是从1970.1.1的零点开始到当前时间的毫秒数,理论上这个可以用来算当前的时间
System.nanoTime() 输出的精度是纳秒级 它的返回值是一个从确定的值算起的,但是这个值是任意的,可能是一个未来的时间,所以返回值有可能是负数
long watchStartNanoTime = System.nanoTime();
Toast.makeText(getBaseContext(), "watchStartNanoTime" + watchStartNanoTime, Toast.LENGTH_SHORT).show(); long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //NANOSECONDS 纳秒 时间单位 一秒的10亿分之一
Toast.makeText(getBaseContext(), "watchDurationMs" + watchDurationMs, Toast.LENGTH_SHORT).show();
UUID.randomUUID()
作用 提供的一个自动生成主键的方法 在一台机器上生成的数字保证对在同一时空中的所有机器都是唯一的
String key = UUID.randomUUID().toString();
Toast.makeText(getBaseContext(), "key" + key, Toast.LENGTH_SHORT).show();
CopyOnWriteArraySet
作用 一个线程安全的容器 底层实现是CopyOnWriteArrayList 适用大小通常保持很小 只读操作远多于可变操作的场景
ReferenceQueue
作用 在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中
后面代码里使用一个弱引用连接到你需要检测的对象,然后使用ReferenceQueue来监测这个弱引用可达性的改变
四大引用
StrongReference
SoftReference
WeakReference
GC线程扫描它所管辖的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
代码示例
A a = new A();
ReferenceQueue queue = new ReferenceQueue();
WeakReference aa = new WeakReference(a, queue);
a = null;
Runtime.getRuntime().gc();
System.runFinalization();
try {
//TOOD线程睡觉
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Reference poll = null;
while ((poll = queue.poll()) != null) {
System.out.println(poll.toString());
}
WeakReference和ReferenceQueue联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就
会把这个弱引用加入到与之关联的引用队列中。上图中的实线a表示强引用,虚线aa表示弱引用。
如果切断a,那么Object对象将会被回收。
当垃圾回收器回收对象的时候,aa这个弱引用将会入队进入ReferenceQueue,所以queue.poll()将
不会为空,除非这个对象没有被垃圾回收器清理。
PhantomReference
代码执行GC操作
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
AOSP Android 开放源代码项目
Java里面的gc拥有各种各样的垃圾收集算法,gc的执行具有很大的不确定性。不管是用System.gc()还是Runtime.getRuntime().gc(),都是对jvm的一个建议,
引发jvm的内部垃圾算法的加权,无法保证gc一定马上执行。所以即使sleep了一定的时间等待gc,仍有极大的可能未执行GC
弱引用算法总结
Object object = new Object(); //强引用
ReferenceQueue queue = new ReferenceQueue();
WeakReference aa = new WeakReference(object, queue); //弱引用aa与强引用object和引用队列queue关联
object = null; //强引用置空 //手动GC算法
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
System.runFinalization(); //如果强引用object被回收 则弱引用aa会放入引用队列中
Reference poll = null;
while ((poll = queue.poll()) != null) {
Toast.makeText(getBaseContext(), "poll.toString()" + poll.toString(), Toast.LENGTH_SHORT).show();
}
将弱引用与要回收的强引用和引用队列关联
如果强引用回收了 则弱引用会放入引用队列中
如果所有强引用都回收了 则无内存泄露 相关联的弱引用则都在引用队列中
问题是手动GC具有很大的不确定性 无法保证GC一定执行
IntentService
作用 一个异步的会自动停止的服务 很好解决了传统Service中处理耗时操作忘记停止销毁Service的问题
底层实现是HandlerThread和Handler的封装
项目API
ActivityRefWatcher
用于监控Activity,但只能用于Android 4.0及其之上 它通过watchActivities方法将全局的Activity生命周期回调接口
Application.ActivityLifecycleCallbacks注册到application
RefWatcher
作用 LeakCanary核心中的核心。RefWatcher的工作就是触发GC,如果对象被回收,那么WeakReference将被放入
ReferenceQueue中,否则就怀疑有泄漏(仅仅是怀疑),然后将内存dump出来,为接下来的深入分析做准备。
watchExecutor: 执行内存泄露检测的executor
debuggerControl :用于查询是否正在调试中,调试中不会执行内存泄露检测
queue : 用于判断弱引用所持有的对象是否已被GC。
gcTrigger: 用于在判断内存泄露之前,再给一次GC的机会
headDumper: 用于在产生内存泄露室执行dump 内存heap
heapdumpListener: 用于分析前面产生的dump文件,找到内存泄露的原因
excludedRefs: 用于排除某些系统bug导致的内存泄露
retainedKeys: 持有那些待检测以及产生内存泄露的引用的key。
工作流程
RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
然后在后台线程检查引用是否被清除,如果没有,调用GC。
如果引用还是未被清除,把 heap 内存 dump 到 文件系统中的一个 .hprof 文件中。
在另外一个进程中,HeapAnalyzerService 通过 HeapAnalyzer 使用HAHA 解析这个文件。
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄漏。
HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否泄漏。如果是,建立导致泄漏的引用链。
引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
ExcludedRef
系统的bug,以及某些厂商rom的bug
AndroidExcludedRefs
枚举了很多特定版本系统issue引起的内存泄漏,因为这种问题不是开发者导致的,分析内存泄露时会排除掉
HeapDump.Listener与ServiceHeapDumpListener
ServiceHeapDumpListener实现了HeapDump.Listener接口。当RefWatcher发现可疑引用的之后,它将dump出来的Hprof文件通过
listener传递到HeapAnalyzerService。
HeapAnalyzerService
主要是通过HeapAnalyzer.checkForLeak分析对象的引用,计算出到GC
root的最短强引用路径。然后将分析结果传递给DisplayLeakService。
AbstractAnalysisResultService与DisplayLeakService
DisplayLeakService继承了AbstractAnalysisResultService。它主要是用来处理分析结果,将结果写入文件,然后在通知栏报警。
开头的两个问题
如何判断一个对象属于内存泄漏
如何在hprof文件里面定位到问题并抓取出来