前言
Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive
1 2 3 4 >+ Active 一般说来内存一开始被分配的状态都是 Active >+ Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象 >+ Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收 >+ Inactive 最终的状态,无法变成其他的状态
ReferenceQueue 引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中
如何检测一个对象是否被回收
1 2 3 创建一个引用队列 queue 创建 Reference 对象(通常用弱引用)并关联引用队列 在 Reference 被回收的时候,Reference 会被添加到 queue 中
1 2 3 4 5 ReferenceQueue queue = new ReferenceQueue(); WeakReference reference = new WeakReference(new Object(), queue); System.gc(); Reference reference1 = queue.remove();
在 Reference 类加载的时候,Java 虚拟机会会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC,变成了不可达。
原理 弱引用: 在垃圾回收时,无论内存是否充足,都会将弱引用包装的对象回收。
当JVM进行垃圾回收时,无论内存是否充足,如果该对象只有弱引用存在,那么该对象会被垃圾回收器回收,同时该引用会被加入到关联的ReferenceQueue。因此程序可以通过判断引用队列中是否已经包含指定的引用,来了解被引用的对象是否被GC回收 (引用队列中存在指定的弱引用,说明对象被回收)。
所以 Leakcanary 在进行内存泄露的监控时,利用弱引用的上述特性,在对象生命周期结束后主动gc并检查该对象的弱引用是否被回收,如果弱引用没有被正常回收,说明在对象生命周期结束以后,该对象还被其他对象持有他的非弱引用。该对象还有到达GC ROOTS的可达路径。如果在对象生命周期结束后弱引用不存在了,说明该对象已经被JVM的垃圾回收器正常回收了,该对象的内存空间也被正常回收。
监听 Activity 的生命周期。
在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。
一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。
dump 出 hprof 文件,找到泄漏路径。
生命周期监测 onDestory 时候对activity进行监测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // ActivityRefWatcher.class public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) { Application application = (Application) context.getApplicationContext(); ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); // 通过 Application 注册 Activity 生命周期回调接口。 application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks); } private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { @Override public void onActivityDestroyed(Activity activity) { // 在Activity 被销毁的时候去进行监听(Activity被销毁后,理应被回收,所以在这里触发监听操作)。 refWatcher.watch(activity); } };
主线程空闲 AndroidWatchExecutor
1 2 3 4 5 6 7 8 9 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; } }); }
监测泄漏 RefWatcher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); // 1.先进行移除(可能这里已经发生过gc操作了) removeWeaklyReachableReferences(); // Debug 模式忽略 if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } // 2.判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。 if (gone(reference)) { return DONE; } // 3.前面表示指定的弱引用没有被回收,因此手动触发GC操作。 gcTrigger.runGc(); //内部触发 Runtime.getRuntime().gc() 操作,同时让当前线程sleep(100,给gc一段处理时间)。 // 4.与步骤1相同的操作 removeWeaklyReachableReferences(); // 5.如果gone()返回false,表示可能存在内存泄漏。 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // 6.导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。 File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); // 7.将导出的信息封装成HeapDump对象 HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); // 8.对HeapDump进行内存泄漏的分析 heapdumpListener.analyze(heapDump); // heapdumpListeners是ServiceHeapDumpListener类型 } return DONE; }
先进行移除(可能这里已经发生过gc操作了)。
判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
前面表示指定的弱引用没有被回收,因此手动触发GC操作。与步骤1相同的操作。
如果gone()返回false,表示可能存在内存泄漏。
导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
将导出的信息封装成HeapDump对象。
对HeapDump进行内存泄漏的分析。
在launcher的显示 1 2 3 4 5 6 7 8 9 <activity android:theme="@style/leak_canary_LeakCanary.Base" android:name=".internal.DisplayLeakActivity" android:process=":leakcanary" android:enabled="false" android:label="@string/leak_canary_display_activity_label" android:icon="@mipmap/leak_canary_icon" android:taskAffinity="com.squareup.leakcanary.${applicationId}" >
1 2 3 4 5 6 7 8 public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) { ComponentName component = new ComponentName(appContext, componentClass); PackageManager packageManager = appContext.getPackageManager(); int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED; // Blocks on IPC. packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP); }