前几天面试,问到了我内存泄漏的相关问题,顺其自然问到了内存泄漏的检测工具LeakCanary的工作原理。当时不会,在看了几篇博文后,明白了其中的道理,理一下思路记录在此。
准备知识
ReferenceQueue
public class RefTest { private static ReferenceQueue<byte[]> rq = new ReferenceQueue<byte[]>(); private static int _1M = 1024*1024; public static void main(String[] args) { Object value = new Object(); Map<Object, Object> map = new HashMap<>(); Thread thread = new Thread(() -> { try { int cnt = 0; WeakReference<byte[]> k; while((k = (WeakReference) rq.remove()) != null) { System.out.println((cnt++) + "回收了:" + k); } } catch(InterruptedException e) { //结束循环 } }); thread.setDaemon(true); thread.start(); for(int i = 0;i < 10000;i++) { byte[] bytes = new byte[_1M]; //注意构造弱引用时传入rq WeakReference<byte[]> weakReference = new WeakReference<byte[]>(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对象即可启动检测内存泄漏功能。那我们从该行代码向内部探究:
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
有一定经验的老程可以看出,这里是标准的建造者模式,每一个方法返回的都是Builder保证以流式调用。那我们看看构造一个RefWatcher需要建造哪些组件
AndroidRefWatchBuilder.listenerServiceClass()
public AndroidRefWatcherBuilder listenerServiceClass( Class<? extends AbstractAnalysisResultService> listenerServiceClass) { return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass)); }
往下走进入AndroidRefWatchBuilder父类方法RefWatcherBuilder.heapDumpListener():
/** @see HeapDump.Listener */ public final T heapDumpListener(HeapDump.Listener heapDumpListener) { this.heapDumpListener = heapDumpListener; return self(); }
可以看到,这里给成员变量heapDumpListener赋值,也就是说,接下来build Watcher时,需要builder提供heapDumpListener。(不熟悉的同学可以先看看建造者模式)这个变量的作用我们暂时不做研究,后续讲。
第二个方法exludedRefs,同样也是给Builder的成员变量赋值:
/** @see ExcludedRefs */ public final T excludedRefs(ExcludedRefs excludedRefs) { this.excludedRefs = excludedRefs; return self(); }
重点是设置好成员变量后,调用buildAndInstall()方法,如下:
public RefWatcher buildAndInstall() { if (LeakCanaryInternals.installedRefWatcher != null) { throw new UnsupportedOperationException("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; return refWatcher; }
build()方法没什么好讲的,完全是build设计模式,构造出一个RefWatcher,我们进入install方法:
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); }
我们看下变量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); } };
可以看到,这里只监控了activityDestory,当有activity被销毁时,交给onActivityDestoryed()处理,onActivityDestory方法:
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
交给watcher处理。到此,我们完成了第一步:监听activity销毁信息
接下来,研究下watch()方法是如何判断activity是否泄漏的,这里便用到了预备知识中所讲的ReferenceQueue。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); }
注意,这里将activity、唯一生成的key、还有queue(是一个ReferenceQueue,在watcher构造时new出来的)构造一个KeyedWeakReference。并将key值存入retainKeys中保管。该类是WeakReference的子类,如下:
final class KeyedWeakReference extends WeakReference<Object> { public final String key; public final String name; KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> 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.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //将已被回收的activity对象的keyedWeakReference的key值从retainedKeys中删除,以达到 //过滤目的 removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } //如果retainedKeys中不存在reference,说明它已经被回收,返回 if (gone(reference)) { return DONE; } //手动调用GC gcTrigger.runGc(); //再次过滤 removeWeaklyReachableReferences(); //若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照 //展示泄漏信息 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } return DONE; }
看下removeWeakReachableReferences()方法:
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; //若queue中存在该keyedWeakReference,则说明该keyedWeakReference对应的activity已被回收 while ((ref = (KeyedWeakReference) queue.poll()) != null) { //从retainedKeys中移除,则retainedKeys剩下的就是泄漏的 retainedKeys.remove(ref.key); } }
gone()方法:
private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); }
这一部分,注释写的比较清楚,就不再赘述,可以对照代码理解。当该activity被判断为泄漏,就要进行第三步:展示泄漏信息,也就是获取dumpheap以及对这个dumpheap进行analyze。调用比较简单,而逻辑实现又是依托另外一个项目:HAHA。调用如下:
//若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照 //展示泄漏信息 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //获取dump文件 File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); //调用heapdumpListener.analyze进行分析 heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); }
这里的heapdumpListener就是第一步构造watcher时,builder中创建的heapdumpListener。追下去可以看到ServiceHeapDumpListener这个类封装了analyze逻辑:
@Override public void analyze(HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }
追进去runAnalysis()方法中就是启动一个服务处理heapDump:
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); }
在HeapAnalyzerService.onHandIntent()中收到启动服务请求,调用checkForLeak:
@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():
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); //将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) { return noLeak(since(analysisStartNanoTime)); } //返回泄漏对象的最短路径 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类对象 ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); List<String> keysFound = new ArrayList<>(); //遍历这个类的所有实例 for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); //key值和最开始定义封装的key值相同,说明该实例是泄漏对象 String keyCandidate = asString(fieldValue(values, "key")); if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); }到这里就分析结束了,其实我们可以从LeakCanary中学习到很多内容,在编码时借鉴,例如建造者模式、解耦、过滤等。