LeakCanary原理
前言
Leakcanary是由Square公司开源的一款轻量的第三方检测内存泄露的工具
主要原理 watch一个即将要销毁的对象,比如监控一个activity处于什么状态。
先来看一下java内存中几个比较重要的部分
- 栈(stack) 存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
- 堆(heap) 主要存放用new产生的数据,是垃圾回收器主要回收的部分
- 方法区 存储每个类的信息(包括类的名称、方法信息、字段信息)静态变量、常量以及编译器变异后的的代码等
为什么会产生内存泄露
当一个对象已经不再使用了,本应该回收,但是一个能到达GCRoot的对象还持有它的引用,导致它无法被回收,还停留在堆内存中,导致内存泄漏
LeakCanary原理:
- 当一个Activity Destory之后,将它放在一个WeakReference弱引用中中
- 把这个WeakReference关联到一个ReferenceQueue
- 查看ReferenceQueue中是否存在Activity的引用
- 如果Activity泄露了,就Dump出heap信息,然后去分析内存泄露的路径
java中的4中引用类型
- 强引用:不会被GC回收
- 软引用:内存不足的时候会被GC回收
- 弱引用:当下次GC的时候会回收
- 虚引用:任何情况都可以回收
ReferenceQueue 引用队列 软引用和弱引用都可以和它集合使用,如果软引用或者弱引用中的对象被垃圾回收了,java虚拟机会吧这个引用加入到与之关联的引用队列当中。
LeakCanary源码分析
一般是在Application的onCreate方法中初始化
@Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); }
进入install方法
public static @NonNull RefWatcher install(@NonNull Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
返回一个RefWatcher对象,这个对象是用来监视应该成为弱引用的对象。最终通过buildAndInstall()这个方法创建出来。
public @NonNull RefWatcher buildAndInstall() { if (LeakCanaryInternals.installedRefWatcher != null) { throw new UnsupportedOperationException("buildAndInstall() should only be called once."); } RefWatcher refWatcher = build(); //如果是在别的进程中,会跟DISABLED相等 if (refWatcher != DISABLED) { if (enableDisplayLeakActivity) { LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true); } //默认为true if (watchActivities) { ActivityRefWatcher.install(context, refWatcher); } //默认为true if (watchFragments) { FragmentRefWatcher.Helper.install(context, refWatcher); } } LeakCanaryInternals.installedRefWatcher = refWatcher; return refWatcher; }
通过build方法创建出RefWatcher,如果是别的进程,就直接返回成员变量DISABLED,如果不是创建新的RefWatcher并返回。
如果允许显示内存泄露的Activity,就设置可显示,DisplayLeakActivity就是当有内存泄露的时候,LeakCanary给我们提供的可视化的那个界面
分别创建ActivityRefWatcher和FragmentRefWatcher,首先看ActivityRefWatcher.install方法
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) { Application application = (Application) context.getApplicationContext(); ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks); } private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { @Override public void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); } };
通过传入的context拿到Application ,并创建ActivityRefWatcher ,最后通过application注册Activity的生命周期回到函数,并传入自己的callback。ActivityLifecycleCallbacksAdapter继承自Android系统的Application.ActivityLifecycleCallbacks接口,主要为了简化代码,因为只用到了onActivityDestroyed这一个方法。这个接口是Andorid系统为我们提供的可以监听到每个Activity的生命周期。
在回调函数中可以看到,当监听到一个Activity销毁的时候,就通过refWatcher.watch(activity)方法把这个Activity关联到RefWatcher中。
在查看watch方法之前,先来看一下RefWatcher有哪些成员变量
public final class RefWatcher { public static final RefWatcher DISABLED = new RefWatcherBuilder().build(); private final WatchExecutor watchExecutor; private final DebuggerControl debuggerControl; private final GcTrigger gcTrigger; private final HeapDumper heapDumper; private final HeapDump.Listener heapdumpListener; private final HeapDump.Builder heapDumpBuilder; private final Set retainedKeys; private final ReferenceQueue queue; ....
- WatchExecutor: 用于执行内存泄露的检测
- DebuggerControl: 判断是否是调试状态,调试状态是不用检测内存泄露的
- GcTrigger: 用来处理GC,当检测到一个对象可能会内存泄露的时候,它会调用其中的方法在手动GC一下,看是否能回收这个对象,如果不能回收那这个对象就泄露了
- HeapDumper: dump出内存泄露的堆文件
- HeapDump.Listener: 用来分析产生heap文件的回调
- HeapDump.Builder:HeapDump的构建者对象
- Set: 集合,持有待检测的和已经产生内存泄露的引用的key
- ReferenceQueue: 判断弱引用所持有的对象是否执行了GC垃圾回收
OK,现在去查看watch方法
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); } final class KeyedWeakReference extends WeakReference {}
创建了一个唯一key,然后放到成员变量set中保存,之后创建了一个KeyedWeakReference这个弱引用。用来保存需要分析的对象,最后执行异步方法ensureGoneAsync来分析这个弱引用对象。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); } }); }
在子线程中执行ensureGone方法来分析对象是否真的被回收了
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); //从我们调用watch方法到现在的总共使用的时间 long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //清除set集合中 已经到达引用队列中的弱引用 removeWeaklyReachableReferences(); //如果在调试状态 就不需要分析 if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } //改对象没有造成内存泄露 if (gone(reference)) { return DONE; } //手动调用GC gcTrigger.runGc(); //再次 清除set集合中 已经到达引用队列中的弱引用 removeWeaklyReachableReferences(); //如果此时引用集合set中还包含改对象,那么它就是个内存泄露的对象 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //demp出一个 .hprof文件 File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); //分析内存泄露 heapdumpListener.analyze(heapDump); } return DONE; }
总结一下前面的代码:
- 创建一个RefWatcher并启动一个ActivityRefWatcher
- 通过ActivityLifecycleCallbacks接口,监听activity的回调,在onDestory中去将activity对象放入观察引用中去观察
- 先清除引用队列中的弱引用,接着检查对象是否到达引用队列,然后手动执行GC,如果GC完后还有未被回收的对象,调用analyze方法分析内存泄露
public interface Listener { Listener NONE = new Listener() { @Override public void analyze(HeapDump heapDump) { } }; void analyze(HeapDump heapDump); } @Override public void analyze(@NonNull HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }
analyze是Listener接口中的一个方法,它的实现类是在ServiceHeapDumpListener中。最后调用了HeapAnalyzerService.runAnalysis方法。
public static void runAnalysis(Context context, HeapDump heapDump, Class listenerServiceClass) { setEnabledBlocking(context, HeapAnalyzerService.class, true); setEnabledBlocking(context, listenerServiceClass, true); Intent intent = new Intent(context, HeapAnalyzerService.class); intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); ContextCompat.startForegroundService(context, intent); }
HeapAnalyzerService 继承自 ForegroundService , ForegroundService 继承自 IntentService,runAnalysis方法中就是开启了一个前台的IntentService。最后会执行IntentService的onHandleIntent方法,这里面又执行了抽象方法onHandleIntentInForeground,这个方法在HeapAnalyzerService类中实现。
@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses); AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize); AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); }
从intent中拿到className和HeapDump,然后通过HeapAnalyzer这个类的checkForLeak方法进行分析。最后通过sendResultToListener方法返回。
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) { long analysisStartNanoTime = System.nanoTime(); if (!heapDumpFile.exists()) { Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile); return failure(exception, since(analysisStartNanoTime)); } try { listener.onProgressUpdate(READING_HEAP_DUMP_FILE); //将heap文件封装成MemoryMappedFileBuffer HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); //创建hprof解析器,解析hprof文件 HprofParser parser = new HprofParser(buffer); listener.onProgressUpdate(PARSING_HEAP_DUMP); //解析生成快照 Snapshot snapshot = parser.parse(); listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS); //去除重复的内容 deduplicateGcRoots(snapshot); listener.onProgressUpdate(FINDING_LEAKING_REF); //找到泄露对象 Instance leakingRef = findLeakingReference(referenceKey, snapshot); // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { String className = leakingRef.getClassObj().getClassName(); return noLeak(className, since(analysisStartNanoTime)); } //找到泄露对象的最短路径 return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
checkForLeak方法就是LeakCanary中的核心方法了,这里面用到了Square的另一个开源库haha库,地址 https://github.com/square/haha
- 通过HprofParser类将hprof转换为Snapshot内存快照。Snapshot中包含所有对象引用的路径,就能查找到内存泄露的路径了
- 优化GCRoot 通过deduplicateGcRoots方法删除重复的路径
- findLeakingReference找出泄露的对象
- findLeakTrace方法找出泄露对象的最短路径
findLeakingReference方法
private Instance findLeakingReference(String key, Snapshot snapshot) { ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); if (refClass == null) { throw new IllegalStateException( "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump."); } List keysFound = new ArrayList(); for (Instance instance : refClass.getInstancesList()) { List values = classInstanceValues(instance); Object keyFieldValue = fieldValue(values, "key"); if (keyFieldValue == null) { keysFound.add(null); continue; } String keyCandidate = asString(keyFieldValue); if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); }
- 在内存快照Snapshot中找到第一个弱引用KeyedWeakReference,这就是内存泄露的对象。
- 遍历这个对象的所有的实例
- 如果找到的key值和最开始保存的key值一样,那么这个对象就是内存泄露的对象
findLeakTrace方法
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef, boolean computeRetainedSize) { listener.onProgressUpdate(FINDING_SHORTEST_PATH); ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); String className = leakingRef.getClassObj().getClassName(); // False alarm, no strong reference path to GC Roots. if (result.leakingNode == null) { return noLeak(className, since(analysisStartNanoTime)); } listener.onProgressUpdate(BUILDING_LEAK_TRACE); LeakTrace leakTrace = buildLeakTrace(result.leakingNode); long retainedSize; if (computeRetainedSize) { listener.onProgressUpdate(COMPUTING_DOMINATORS); // Side effect: computes retained size. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; retainedSize = leakingInstance.getTotalRetainedSize(); // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer if (SDK_INT <= N_MR1) { listener.onProgressUpdate(COMPUTING_BITMAP_SIZE); retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); } } else { retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED; } return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); }
通过findPath方法,GCroot开始往下寻找
LeakTrace就是内存泄露的调用栈
getTotalRetainedSize()方法,计算内存泄露的内存空间大小
OK Activity的监控流程就看完啦,下面看一下Fragment的。其实跟Activity差不多。从install开始
public static void install(Context context, RefWatcher refWatcher) { List fragmentRefWatchers = new ArrayList(); // 如果大于Anroid 26,需要增加AndroidOFragmentRefWatcher if (SDK_INT >= O) { fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher)); } // 通过反射添加SupportFragmentRefWatcher try { Class fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME); Constructor constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class); FragmentRefWatcher supportFragmentRefWatcher = (FragmentRefWatcher) constructor.newInstance(refWatcher); fragmentRefWatchers.add(supportFragmentRefWatcher); } catch (Exception ignored) { } if (fragmentRefWatchers.size() == 0) { return; } Helper helper = new Helper(fragmentRefWatchers); Application application = (Application) context.getApplicationContext(); application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks); }
通过反射找到SupportFragmentRefWatcher,它类需要在build.gradle中添加 debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'加入引用。
最后注册ActivityLifecycleCallbacks,来监听activity的回调
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { for (FragmentRefWatcher watcher : fragmentRefWatchers) { watcher.watchFragments(activity); } } };
可以看到这里监听的是activity的onActivityCreated这个生命周期函数,然后把当前的activity的对象传入FragmentRefWatcher中,执行接口watchFragments。SupportFragmentRefWatcher和AndroidOFragmentRefWatcher是FragmentRefWatcher的实现类。最终会回调实现类的watchFragments方法
@Override public void watchFragments(Activity activity) { FragmentManager fragmentManager = activity.getFragmentManager(); fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true); }
通过Activity找到FragmentManager,然后注册系统的Fragment的生命周期回调监听
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() { @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) { View view = fragment.getView(); if (view != null) { refWatcher.watch(view); } } @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) { refWatcher.watch(fragment); } };
Fragment的回调主要监听了onFragmentViewDestroyed和onFragmentDestroyed两个回调方法。最终都会调用RefWatcher中的watch方法,这里面跟前面activity中是一样的啦。