「Leakcanary 」是我们经常用于检测内存泄漏的工具,简单的使用方式,内存泄漏的可视化,是我们开发中必备的工具之一。
分析源码之前
Leakcanary 大神的 github ,最好的老师。
一、使用
1、配置
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
2、简单使用
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;
}
LeakCanary.install(this);
// Normal app init code...
}
}
超级简单的配置和使用方式。最后就会得出以下的事例说明。
二、准备工作
1、Reference
Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。
- Active 一般说来内存一开始被分配的状态都是 Active
- Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
- Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
- Inactive 最终的状态,无法变成其他的状态。
2、ReferenceQueue
引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中
3、如果检测一个对象是否被回收
需要采用 Reference + ReferenceQueue
- 创建一个引用队列 queue
- 创建 Reference 对象(通常用弱引用)并关联引用队列
- 在 Reference 被回收的时候,Reference 会被添加到 queue 中
//创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();
// 创建弱引用,此时状态为Active,并且Reference.pending为空,
// 当前Reference.queue = 上面创建的queue,并且next=null
// reference 创建并关联 queue
WeakReference reference = new WeakReference(new Object(), queue);
// 当GC执行后,由于是弱引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();
// ReferenceHandler从 pending 中取下该元素,并且将该元素放入到queue中,
//此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED
// 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL
Reference reference1 = queue.remove();
在 Reference 类加载的时候,Java 虚拟机会会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC,变成了不也达。
4、ActivityLifecycleCallbacks
用于监听所有 Activity 生命周期的回调方法。
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);
}
};
5、Heap Dump
Heap Dump也叫堆转储文件,是一个Java进程在某个时间点上的内存快照。
三、原理说明
1、监听 Activity 的生命周期。
2、在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。
3、一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。
4、dump 出 hprof 文件,找到泄漏路径。
分析源码
程序的唯一入口 LeakCanary.install(this);
1、install
DisplayLeakService 这个类负责发起 Notification 以及将结果记录下来写在文件里面。以后每次启动LeakAnalyzerActivity就从这个文件里读取历史结果,并展示给我们。
public static RefWatcher install(Application application) {
return install(application, DisplayLeakService.class);
}
public static RefWatcher install(Application application,
Class extends AbstractAnalysisResultService> listenerServiceClass) {
//如果在主线程 那么返回一个无用的 RefWatcher 详解 1.1
if (isInAnalyzerProcess(application)) {
return RefWatcher.DISABLED;
}
//把 DisplayLeakActivity 设置为可用 用于显示 DisplayLeakActivity 就是我们看到的那个分析界面
enableDisplayLeakActivity(application);
// 详解 1.2
HeapDump.Listener heapDumpListener =
new ServiceHeapDumpListener(application, listenerServiceClass);
//详解 2
RefWatcher refWatcher = androidWatcher(application, heapDumpListener);
详解 3
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}
1.1 isInAnalyzerProcess
因为 分析的进程是硬外一个独立进程 所以要判断是否是主进程,这个工作需要在 AnalyzerProcess 中进行。
public static boolean isInAnalyzerProcess(Context context) {
return isInServiceProcess(context, HeapAnalyzerService.class);
}
把App 的进程 和 这个 Service 进程进行对比 。
private static boolean isInServiceProcess(Context context,
Class extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
Log.e("AndroidUtils", "Could not get package info for " + context.getPackageName(), e);
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
if (serviceInfo.processName.equals(mainProcess)) {
Log.e("AndroidUtils",
"Did not expect service " + serviceClass + " to run in main process " + mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
for (ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses()) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
if (myProcess == null) {
Log.e("AndroidUtils", "Could not find running process for " + myPid);
return false;
}
//把App 的进程 和 这个 Service 进程进行对比
return myProcess.processName.equals(serviceInfo.processName);
}
1.2 ServiceHeapDumpListener
设置 DisplayLeakService 和 HeapAnalyzerService 的可用。
analyze 方法,开始分析 HeapDump。
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(Context context,
Class extends AbstractAnalysisResultService> listenerServiceClass) {
LeakCanary.setEnabled(context, listenerServiceClass, true);
LeakCanary.setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
2、RefWatcher
private final Executor watchExecutor;
private final DebuggerControl debuggerControl;
private final GcTrigger gcTrigger;
private final HeapDumper heapDumper;
private final Set retainedKeys;
private final ReferenceQueue
- watchExecutor:执行内存泄漏检测的 Executor。
- debuggerControl:用于查询是否在 debug 调试模式下,调试中不会执行内存泄漏检测。
- gcTrigger:GC 开关,调用系统GC。
- heapDumper:用于产生内存泄漏分析用的 dump 文件。即 dump 内存 head。
- retainedKeys:保存待检测和产生内存泄漏的引用的 key。
- queue:用于判断弱引用持有的对象是否被 GC。
- heapdumpListener:用于分析 dump 文件,生成内存泄漏分析报告。
这里创建我们所需要的 RefWatcher。
public static RefWatcher androidWatcher(Application app, HeapDump.Listener heapDumpListener) {
DebuggerControl debuggerControl = new AndroidDebuggerControl();
AndroidHeapDumper heapDumper = new AndroidHeapDumper(app);
heapDumper.cleanup();
return new RefWatcher(new AndroidWatchExecutor(), debuggerControl, GcTrigger.DEFAULT,
heapDumper, heapDumpListener);
}
3、ActivityRefWatcher
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}
//注册 lifecycleCallbacks
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
//ArrayList mActivityLifecycleCallbacks
//就是 mActivityLifecycleCallbacks 的添加
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.add(callback);
}
}
// 注销 lifecycleCallbacks
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
// //就是 mActivityLifecycleCallbacks 的 移除
public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.remove(callback);
}
}
本质就是在 Activity 的 onActivityDestroyed 方法里 执行 refWatcher.watch(activity);
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);
}
4、watch
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
if (debuggerControl.isDebuggerAttached()) {
return;
}
//随机生成 watchedReference 的 key 保证其唯一性
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
//这个一个弱引用的子类拓展类 用于 我们之前所说的 watchedReference 和 queue 的联合使用
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() {
@Override public void run() {
//重要方法,确然是否 内存泄漏
ensureGone(reference, watchStartNanoTime);
}
});
}
final class KeyedWeakReference extends WeakReference
5、ensureGone
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//把 queue 的引用 根据 key 从 retainedKeys 中引出 。
//retainedKeys 中剩下的就是没有分析和内存泄漏的引用的 key
removeWeaklyReachableReferences();
//如果内存没有泄漏 或者处于 debug 模式那么就直接返回
if (gone(reference) || debuggerControl.isDebuggerAttached()) {
return;
}
//如果内存依旧没有被释放 那么在 GC 一次
gcTrigger.runGc();
//再次 清理下 retainedKeys
removeWeaklyReachableReferences();
//最后还有 就是说明内存泄漏了
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump 出 Head 报告
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == null) {
// Could not dump the heap, abort.
return;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//最后进行分析 这份 HeapDump
//LeakCanary 分析内存泄露用的是一个第三方工具 HAHA 别笑 真的是这个名字
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
heapDumpDurationMs));
}
}
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);
}
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
6、haha
大家有兴趣可以分析下这个分析库的原理。在这里就不深入研究了。
最后把分析的引用链 写入文件中,发通知。
@TargetApi(HONEYCOMB) @Override
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result);
Log.d("LeakCanary", leakInfo);
if (!result.leakFound || result.excludedLeak) {
afterDefaultHandling(heapDump, result, leakInfo);
return;
}
File leakDirectory = DisplayLeakActivity.leakDirectory(this);
int maxStoredLeaks = getResources().getInteger(R.integer.__leak_canary_max_stored_leaks);
File renamedFile = findNextAvailableHprofFile(leakDirectory, maxStoredLeaks);
if (renamedFile == null) {
// No file available.
Log.e("LeakCanary",
"Leak result dropped because we already store " + maxStoredLeaks + " leak traces.");
afterDefaultHandling(heapDump, result, leakInfo);
return;
}
heapDump = heapDump.renameFile(renamedFile);
File resultFile = DisplayLeakActivity.leakResultFile(renamedFile);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(resultFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(heapDump);
oos.writeObject(result);
} catch (IOException e) {
Log.e("LeakCanary", "Could not save leak analysis result to disk", e);
afterDefaultHandling(heapDump, result, leakInfo);
return;
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException ignored) {
}
}
}
PendingIntent pendingIntent =
DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
String contentTitle =
getString(R.string.__leak_canary_class_has_leaked, classSimpleName(result.className));
String contentText = getString(R.string.__leak_canary_notification_message);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification;
if (SDK_INT < HONEYCOMB) {
notification = new Notification();
notification.icon = R.drawable.__leak_canary_notification;
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(this, contentTitle, contentText, pendingIntent);
} else {
Notification.Builder builder = new Notification.Builder(this) //
.setSmallIcon(R.drawable.__leak_canary_notification)
.setWhen(System.currentTimeMillis())
.setContentTitle(contentTitle)
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
if (SDK_INT < JELLY_BEAN) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
}
notificationManager.notify(0xDEAFBEEF, notification);
afterDefaultHandling(heapDump, result, leakInfo);
}
总结
其实沿着源码分析很容易让人无法自拔,所以我们更要跳出来看到本质。
1、监听 Activity 的生命周期,在 onDestory 方法里调用 RefWatcher 的 watch 方法。
watch 方法监控的是 Activity 对象
2、给Activyty 的 Reference 生成唯一性的 key 添加到 retainedKeys 。生成 KeyedWeakReference 对象 ,Activity 的弱引用和 ReferenceQueue 关联。执行 ensureGone 方法。
3、如果 retainedKeys 中没有 该 Reference 的 key 那么就说明没有内存泄漏。
4、如果有,那么 analyze 分析我们 HeadDump 文件。建立导致泄漏的引用链。
5、引用链传递给 APP 进程的 DisplayLeakService,以通知的形式展示出来。
最后
「云开方见日,潮尽炉峰出。」