安卓LeakCanary原理解析
前几天面试,问到了我内存泄漏的相关问题,顺其自然问到了内存泄漏的检测工具LeakCanary的工作原理。当时不会,在看了几篇博文后,明白了其中的道理,理一下思路记录在此。
准备知识
ReferenceQueue
引用队列,对于弱引用和软引用来说,若需要知道该引用是否已被GC回收,那么在构造WeakReference或SoftReference时,传入一个ReferenceQueue。那么当这个引用被回收后,就可以通过ReferenceQueue.poll()或remove()拿到该引用,做额外处理,例如清理数据等。下面看一个例子,也是网上唯一关于ReferenceQueue的例子,很好理解,直接拿过来用。
publicclassRefTest{
privatestaticReferenceQueue rq =newReferenceQueue();
privatestaticint_1M =1024*1024;
publicstaticvoidmain(String[] args){
Objectvalue=newObject();
Map map =newHashMap<>();
Thread thread =newThread(() -> {
try{
intcnt =0;
WeakReference k;
while((k = (WeakReference) rq.remove()) !=null) {
System.out.println((cnt++) +"回收了:"+ k);
}
}catch(InterruptedException e) {
//结束循环
}
});
thread.setDaemon(true);
thread.start();
for(inti =0;i <10000;i++) {
byte[] bytes =newbyte[_1M];
//注意构造弱引用时传入rq
WeakReference weakReference =newWeakReference(bytes, rq);
map.put(weakReference,value);
}
System.out.println("map.size->"+ map.size());
}
}
结果:
9992回收了:java.lang.ref.WeakReference@1d13cd4
9993回收了:java.lang.ref.WeakReference@118b73a
9994回收了:java.lang.ref.WeakReference@1865933
9995回收了:java.lang.ref.WeakReference@ad82c
map.size->10000
检测泄漏步骤
一般情况下,检测activity是否泄漏,首先第一步,获得activity被销毁的信息,也就是要监听到activity的生命周期方法。接下来,既然这个activity已经被destory了,那么它理应被GC回收,也就是第二步,判断该对象(activity)是否被回收。若回收了那就是没泄漏,若判断该对象没被回收,说明该对象泄漏了,进行第三步,展示泄漏信息。总结下:
1、监听activity被销毁的消息
2、判断该activity是否被销毁
3、根据步骤2结果展示泄漏信息
从LeakCanary源码分析原理
对于应用来说,在Application.onCreate()中调用一句LeakCanary.install(this)即可,也就是说对于LeakCanary来说,传入一个application对象即可启动检测内存泄漏功能。那我们从该行代码向内部探究:
publicstaticRefWatcherinstall(Application application){
returnrefWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
有一定经验的老程可以看出,这里是标准的建造者模式,每一个方法返回的都是Builder保证以流式调用。那我们看看构造一个RefWatcher需要建造哪些组件
AndroidRefWatchBuilder.listenerServiceClass()
publicAndroidRefWatcherBuilder listenerServiceClass(
ClasslistenerServiceClass){
returnheapDumpListener(newServiceHeapDumpListener(context, listenerServiceClass));
}
往下走进入AndroidRefWatchBuilder父类方法RefWatcherBuilder.heapDumpListener():
/**@seeHeapDump.Listener */
publicfinalT heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
returnself();
}
可以看到,这里给成员变量heapDumpListener赋值,也就是说,接下来build Watcher时,需要builder提供heapDumpListener。(不熟悉的同学可以先看看建造者模式)这个变量的作用我们暂时不做研究,后续讲。
第二个方法exludedRefs,同样也是给Builder的成员变量赋值:
/**@seeExcludedRefs */
publicfinalT excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
returnself();
}
重点是设置好成员变量后,调用buildAndInstall()方法,如下:
publicRefWatcherbuildAndInstall(){
if(LeakCanaryInternals.installedRefWatcher !=null) {
thrownewUnsupportedOperationException("buildAndInstall() should only be called once.");
}
//调用build方法构造
RefWatcher refWatcher = build();
if(refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
if(watchActivities) {
//传入build好的RefWatch,调用静态方法进行install
ActivityRefWatcher.install((Application) context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
returnrefWatcher;
}
build()方法没什么好讲的,完全是build设计模式,构造出一个RefWatcher,我们进入install方法:
publicstaticvoidinstall(Application application, RefWatcher refWatcher){
newActivityRefWatcher(application, refWatcher).watchActivities();
}
构造ActivityRefWatcher并调用watchActivities()监控activity,其实我们可以从ActivityRefWatcher这个类的名字看出,它是负责监控Activity的,那么,我们想想作为第三方应用来说,如何监控应用内的每一个Activity?可以通过系统提供给我们的接口ActivityLifecycleCallbacks,该接口的回调方法与activity生命周期回调方法对应,每当有activity创建或销毁时,可以通过回调方法通知外部,watchActivities方法每部就是采用的这个接口:
publicvoidwatchActivities(){
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
我们看下变量lifecycleCallbacks:
privatefinalApplication.ActivityLifecycleCallbacks lifecycleCallbacks =
newApplication.ActivityLifecycleCallbacks() {
@OverridepublicvoidonActivityCreated(Activity activity, Bundle savedInstanceState){
}
@OverridepublicvoidonActivityStarted(Activity activity){
}
@OverridepublicvoidonActivityResumed(Activity activity){
}
@OverridepublicvoidonActivityPaused(Activity activity){
}
@OverridepublicvoidonActivityStopped(Activity activity){
}
@OverridepublicvoidonActivitySaveInstanceState(Activity activity, Bundle outState){
}
@OverridepublicvoidonActivityDestroyed(Activity activity){
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
可以看到,这里只监控了activityDestory,当有activity被销毁时,交给onActivityDestoryed()处理,onActivityDestory方法:
voidonActivityDestroyed(Activity activity){
refWatcher.watch(activity);
}
交给watcher处理。到此,我们完成了第一步:监听activity销毁信息
接下来,研究下watch()方法是如何判断activity是否泄漏的,这里便用到了预备知识中所讲的ReferenceQueue。watch方法:
publicvoidwatch(Object watchedReference, String referenceName){
if(this== DISABLED) {
return;
}
checkNotNull(watchedReference,"watchedReference");
checkNotNull(referenceName,"referenceName");
finallongwatchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
finalKeyedWeakReference reference =
newKeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
注意,这里将activity、唯一生成的key、还有queue(是一个ReferenceQueue,在watcher构造时new出来的)构造一个KeyedWeakReference。并将key值存入retainKeys中保管。该类是WeakReference的子类,如下:
finalclassKeyedWeakReferenceextendsWeakReference
publicfinalString key;
publicfinalString name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue
super(checkNotNull(referent,"referent"), checkNotNull(referenceQueue,"referenceQueue"));
this.key = checkNotNull(key,"key");
this.name = checkNotNull(name,"name");
}
}
这里,我们将activity包装弱引用,并添加了referenceQueue,那么当该activity被GC回收时,我们就可以从referenceQueue中获取该activity的reference。
接下来直接看核心方法ensureGone
Retryable.ResultensureGone(finalKeyedWeakReference reference,finallongwatchStartNanoTime){
longgcStartNanoTime = System.nanoTime();
longwatchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//将已被回收的activity对象的keyedWeakReference的key值从retainedKeys中删除,以达到
//过滤目的
removeWeaklyReachableReferences();
if(debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
returnRETRY;
}
//如果retainedKeys中不存在reference,说明它已经被回收,返回
if(gone(reference)) {
returnDONE;
}
//手动调用GC
gcTrigger.runGc();
//再次过滤
removeWeaklyReachableReferences();
//若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照
//展示泄漏信息
if(!gone(reference)) {
longstartDumpHeap = System.nanoTime();
longgcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if(heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
returnRETRY;
}
longheapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
newHeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
returnDONE;
}
看下removeWeakReachableReferences()方法:
privatevoidremoveWeaklyReachableReferences(){
// 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.
KeyedWeakReferenceref;
//若queue中存在该keyedWeakReference,则说明该keyedWeakReference对应的activity已被回收
while((ref= (KeyedWeakReference) queue.poll()) !=null) {
//从retainedKeys中移除,则retainedKeys剩下的就是泄漏的
retainedKeys.remove(ref.key);
}
}
gone()方法:
privatebooleangone(KeyedWeakReference reference){
return!retainedKeys.contains(reference.key);
}
这一部分,注释写的比较清楚,就不再赘述,可以对照代码理解。当该activity被判断为泄漏,就要进行第三步:展示泄漏信息,也就是获取dumpheap以及对这个dumpheap进行analyze。调用比较简单,而逻辑实现又是依托另外一个项目:HAHA。调用如下:
//若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照
//展示泄漏信息
if(!gone(reference)) {
longstartDumpHeap = System.nanoTime();
longgcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//获取dump文件
File heapDumpFile = heapDumper.dumpHeap();
if(heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
returnRETRY;
}
longheapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//调用heapdumpListener.analyze进行分析
heapdumpListener.analyze(
newHeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
这里的heapdumpListener就是第一步构造watcher时,builder中创建的heapdumpListener。追下去可以看到ServiceHeapDumpListener这个类封装了analyze逻辑:
@Overridepublicvoidanalyze(HeapDump heapDump){
checkNotNull(heapDump,"heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
追进去runAnalysis()方法中就是启动一个服务处理heapDump:
publicstaticvoid runAnalysis(Context context, HeapDump heapDump,
ClasslistenerServiceClass){
Intent intent =newIntent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
在HeapAnalyzerService.onHandIntent()中收到启动服务请求,调用checkForLeak:
@Override
protectedvoidonHandleIntent(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 =newHeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
关键方法checkForLeak():
publicAnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if(!heapDumpFile.exists()) {
Exceptionexception=newIllegalArgumentException("File does not exist: "+ heapDumpFile);
returnfailure(exception, since(analysisStartNanoTime));
}
try{
HprofBuffer buffer =newMemoryMappedFileBuffer(heapDumpFile);
HprofParser parser =newHprofParser(buffer);
//将heapDumpFile转化为Snapshot
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
//找到泄漏对象引用
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if(leakingRef ==null) {
returnnoLeak(since(analysisStartNanoTime));
}
//返回泄漏对象的最短路径
returnfindLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
}catch(Throwable e) {
returnfailure(e, since(analysisStartNanoTime));
}
}
上面的checkForLeak方法就是输入.hprof,输出分析结果,主要有以下几个步骤:
1.把.hprof转为Snapshot,这个Snapshot对象就包含了对象引用的所有路径
2.精简gcroots,把重复的路径删除,重新封装成不重复的路径的容器
3.找出泄漏的对象
4.找出泄漏对象的最短路径
这里重点分析一下第三步:
privateInstancefindLeakingReference(String key, Snapshot snapshot){
//找到快照中的KeyedWeakReference类对象
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List keysFound =newArrayList<>();
//遍历这个类的所有实例
for(Instance instance : refClass.getInstancesList()) {
List values = classInstanceValues(instance);
//key值和最开始定义封装的key值相同,说明该实例是泄漏对象
String keyCandidate = asString(fieldValue(values,"key"));
if(keyCandidate.equals(key)) {
returnfieldValue(values,"referent");
}
keysFound.add(keyCandidate);
}
thrownewIllegalStateException(
"Could not find weak reference with key "+ key +" in "+ keysFound);
}
到这里就分析结束了,其实我们可以从LeakCanary中学习到很多内容,在编码时借鉴,例如建造者模式、解耦、过滤等。
欢迎大家加群交流技术:367685933