序、慢慢来才是最快的方法
LeakCanary系列
LeakCanary(1)前传
LeakCanary是Square的开源库,通过弱引用方式侦查Activity或Fragment对象的生命周期,若发现内存泄漏自动 dump Hprof文件,通过HAHA库得到泄露的最短路径,最后通过Notification展示。
简单说就是在在Activity对象onDestory的时候,新建一个WeakReference对象指向Activity对象,如果Activity对象被垃圾回收的话,WeakReference对象就会进入引用序列的ReferenceQueue。
所以我们只需要在Activity对象OnDestory之后去查看ReferenceQueue序列是否有该WeakReference对象即可。
第一次观察是Activity的onDestory5秒后,如果发现ReferenceQueue对来还没有WeakReference对象,就进入第二次观察,如果有了,就证明没有泄漏,第二次观察跟第一次观察相比区别在于会先进行垃圾回收,在进行ReferenceQueue序列的观察。
private fun setupLeakCanary(): RefWatcher {
return if (LeakCanary.isInAnalyzerProcess(this)) {
RefWatcher.DISABLED
} else LeakCanary.install(this)
}
//构建者模式,创建对象RefWatcher
//参数listenerServiceClass:分析Hprof的service
//参数AndroidExcludeRefs:过滤已知的内存泄漏问题
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
构建RefWahcher对象,注册Anctivty和Fragment的监听器
public RefWatcher buildAndInstall() {
//只允许install一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// 建造者模式的最后一步,构造对象
RefWatcher refWatcher = build();
//判断是否开启了 LeakCanary,没有开启默认会返回 DISABLED 对象
if (refWatcher != DISABLED) {
// 是否检测 Activity 的 内存泄漏,默认开启
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 是否检测 Fragment 的 内存泄漏,默认开启
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
public static void install(Context context, 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);
}
};
ActivityRefWatcher的静态方法install传入了当前的Application,然后注册了Activity生命周期的监听回调,ActivityLifcyclerCallbacks。里面只重写了Activity的销毁onActivityDestoryed()。在监听里面调用RefWatcher的watch方法。
public static void install(Context context, RefWatcher refWatcher) {
List fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
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);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
FragmentRefWatcher.Helper 的静态方法 install 里同样会注册一个 ActivityLifecycleCallbacks 用于监听 Activity 生命周期中的 onActivityCreated
的创建完成的回调,在 Activity 创建完成后,会对这个 Activity 注册 Fragment 的生命周期监听器。
install 方法首先会判断系统是否大于等于 Android O, 如果是那么会使用 android.app.FragmentManager
进行注册,如果需要兼容 Android O 以下需要自行在依赖中添加对 leakcanary-support-fragment
组件的依赖,然后通过反射构造出SupportFragmentRefWatcher
; 然后将fragmentRefWatchers所有监听器取出,在 Activity 创建完成后,添加 Fragment 的生命监听,主要关注 Fragment 的 onFragmentViewDestroyed
和 onFragmentDestroyed
方法。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
//省略
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);
}
首先生成一个UUID唯一的key,LeakCanary构造了一个带有key的弱引用对象,并将queue设置为弱引用对象的引用队列。
很简单,假设CommonActivity发生了内存泄漏,系统执行GC操作是,肯定不会回收当前对象,这样WeakReference对象也不会被回收。如果启动了多了CommonActivity,分析Hprof文件时我们可以获取所有的CommonActivity对象,当年我们去检测其中某一个具体的Activity时,就会出现无法匹配。但是如果使用了带有 key 的 WeakReference 对象,发生泄露时泄漏时,key 的值也会 dump 保存下来,这样我们根据 key 的一一对应关系就能映射到某一个 Activity。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// watchExecutor 是 AndroidWatchExecutor的一个实例
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
ensureGoneAsync 方法构造了一个 Retryable 对象,并将它传给 watchExecutor 的 execute 方法。最红调用的是AndroidWatcherExecutor的execute方法
/**
*判断当前线程是不是主线程,如果是主线程直接执行waitForIDle方法
*否则执行postWaitForIdle方法
*
*/
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
watchExecutor
是 AndroidWatchExecutor 的一个实例, AndroidWatchExecutor 的 execute 方法的作用就是判断当前线程是否是主线程,如果是主线程,那么直接执行 waitForIdle 方法,否则通过 Handler 的 post 方法切换到主线程再执行 waitForIdle 方法。
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
waitForIdle
方法通过调用 addIdleHandler
方法,指定当主进程中没有需要处理的事件时,在这个空闲期间执行 postToBackgroundWithDelay
方法。
PS:此处也是使用了Handler的IdleHandler的回调。
postToBackgroundWithDelay
方法每次执行会指数级增加延时时间,延时时间到了后,会执行 Retryable 里的方法,如果返回为重试,那么会增加延时时间并执行下一次。
一直在重试的代码正式 ensureGone
方法。
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除所有弱引用可达对象
removeWeaklyReachableReferences();
// 判断当前是否正在开启USB调试,LeakCanary 的解释是调试时可能会触发不正确的内存泄漏
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 上面执行 removeWeaklyReachableReferences 方法,判断是不是监视对象已经被回收了,
//如果被回收了,那么说明没有发生内存泄漏,直接结束
if (gone(reference)) {
return DONE;
}
// 手动触发一次 GC 垃圾回收
gcTrigger.runGc();
// 再次移除所有弱引用可达对象
removeWeaklyReachableReferences();
// 如果对象没有被回收
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//heapDumper是AndroidHeapDumper,调用dumpHeap方法
//使用 Debug 类 dump 当前堆内存中对象使用情况
File heapDumpFile = heapDumper.dumpHeap();
// dumpHeap 失败的话,会走重试机制
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 将hprof文件、key等属性构造一个 HeapDump 对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// heapdumpListener 分析 heapDump 对象
heapdumpListener.analyze(heapDump);
}
return DONE;
}
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);
}
}
还记得我们在 refWatcher.watch 方法保存了当前监视对象的 ref.key 了么,如果这个对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,通过 poll 操作就可以取出这个弱引用,这时候我们从retainedKeys
中移除这个 key, 代表这个对象已经被正常回收,不需要再被监视了。
8.2dumpHeap方法
public File dumpHeap() {
// 生成一个存储 hprof 的文件
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
// 文件创建失败
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
// FutureResult 内部有一个 CountDownLatch,用于倒计时
FutureResult waitingForToast = new FutureResult<>();
// 切换到主线程显示 toast
showToast(waitingForToast);
// 等待5秒,确保 toast 已完成显示
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
// 创建一个通知
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
// 开始 dump 内存到指定文件
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
heapdumpListener 是 ServiceHeapDumpListener 的一个对象,最终执行了HeapAnalyzerService.runAnalysis
方法。
public static void runAnalysis(Context context, HeapDump heapDump,
Class extends AbstractAnalysisResultService> 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 继承自 IntentService,IntentService的具体原理我就不多做解释了。IntentService会将所有并发的启动服务操作,变成顺序执行 onHandleIntent 方法。
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
// 监听 hprof 文件分析结果的类
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
// hprof 文件类
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// checkForLeak 会调用 haha 组件中的工具,分析 hprof 文件
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 将分析结果发送给监听器 listenerClassName
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
public AnalysisResult checkForLeak(File heapDumpFile, 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);
// 将 hprof 文件解析成 Snapshot
//注意MemoryMappedFiledBuffer这个类
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
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) {
return noLeak(since(analysisStartNanoTime));
}
// 找到泄漏处的引用关系链
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
hprof 文件的解析是由开源项目 haha 完成的。
查找内存泄漏项
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 从 hprof 文件保存的对象中找到所有 KeyedWeakReference 的实例
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<>();
// 对 KeyedWeakReference 实例列表进行遍历
for (Instance instance : refClass.getInstancesList()) {
// 获取每个实例里的所有字段
List values = classInstanceValues(instance);
// 找到 key 字段对应的值
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
String keyCandidate = asString(keyFieldValue);
// 如果这个对象的 key 和 我们查找的 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);
}
查找泄漏处的调用链
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);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
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));
}
1 LeakCanary.install(application);
此时使用application
进行registerActivityLifecycleCallbacks
,从而来监听Activity
的何时被destroy
。
2 在onActivityDestroyed(Activity activity)
的回调中,去检测Activity
是否被回收,检测方式如以下步骤。
3 使用一个弱引用WeakReference
指向这个activity
,并且给这个弱引用指定一个引用队列queue
,同时创建一个key
来标识该activity
。
4 然后将检测的方法ensureGone()
投递到空闲消息队列。
5 当空闲消息执行的时候,去检测queue
里面是否存在刚刚的弱引用,如果存在,则说明此activity
已经被回收,就移除对应的key
,没有内存泄漏发生。
6 如果queue
里不存在刚刚的弱引用,则手动进行一次gc
。
7 gc
之后再次检测queue
里面是否存在刚刚的弱引用,如果不存在,则说明此activity
还没有被回收,此时已经发生了内存泄漏,直接dump
堆栈信息并打印日志,否则没有发生内存泄漏,流程结束。
Android 开源库 #7 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!
被问到:如何检测线上内存泄漏,通过 LeakCanary 探究!
快手KOOM高性能线上解决方案
04 | 内存优化(下):内存优化这件事,应该从哪里着手?