LeakCanary原理的简单理解和使用
- 1、背景
- 2、LeakCanary
- 2.1、LeakCanary 工作原理
- 2.1.1、检测未被 GC 回收的对象
- 2.1.2、转储堆
- 2.1.3、分析堆
- 2.1.4、对泄漏进行分类
- 2.2、LeakCanary 使用
- 2.2.1、引入依赖
- 2.2.1.1、原理
- 2.2.2、配置 LeakCanary
- 2.2.3、检测内存泄漏
- 3、Fragment 和 Activity 的监听
- 4、源码分析
- 4.1、ReferenceQueue说明
- 4.2、注册监听入口(第三节)
- 4.3、Watcher和Activity的监测时机
- 4.4、Fragment的监测时机
- 4.5、ViewModel的检测时机
- 4.6、总结
- 5、优缺陷
- 6、简单实现leakcanary,简化版,没有dump
- 参考
1、背景
LeakCanary,由Square开源的一款轻量第三方内存泄露检测工具。能够在不影响程序正常运行的情况下,动态收集程序存在的内存泄露问题。小的内存泄露可能不会直接导致程序崩溃,但随着数量增多,量变引起质变,造成内存溢出,程序崩溃。
2、LeakCanary
2.1、LeakCanary 工作原理
安装 LeakCanary 后,它会自动检测并报告内存泄漏,分为以下 4 个步骤:
- 1、检测未被 GC 回收的对象
- 2、转储堆
- 3、分析堆
- 4、对泄漏进行分类
2.1.1、检测未被 GC 回收的对象
LeakCanary Hook 到 Android lifecycle 以自动检测 Activitis 和 Fragments 何时被 Destroy 并且被 GC 回收。这些被 Destroy 的对象被传递给一个 ObjectWatcher,它持有对它们的弱引用。LeakCanary 能够自动检测以下对象的泄漏:
- 1、被销毁的 Activity实例
- 2、被销毁的 Fragment实例
- 3、被销毁的 fragmentView实例
- 4、被清除ViewModel实例
可以查看任意一个不再使用的对象,例如 detached view 或 destroyed presenter:
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
如果在等待 5 秒并运行 GC 回收后,ObjectWatcher持有的弱引用没有被清除,则该对象被认为是未被回收的,并且可能会产生泄漏。LeakCanary 就会将这些对象记录到 Logcat:
D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
(Activity received Activity#onDestroy() callback)
... 5 seconds later ...
D LeakCanary: Scheduling check for retained objects because found new object
retained
LeakCanary 在转储堆之前等待未被回收对象(retained objects)的计数达到阈值,并显示具有最新计数的通知。
D LeakCanary: Rescheduling check for retained objects in 2000ms because found only 4 retained objects (< 5 while app visible)
App 处于前台时默认阈值为 5 个 retained objects,App 处于后台时默认阈值为 1 个 retained object。如果看到 retained objects通知,然后将 App 置于后台(例如通过按下 Home 按钮),则阈值从 5 变为 1,并且 LeakCanary 会在 5 秒内转储堆。点击通知会强制 LeakCanary 立即转储堆。
2.1.2、转储堆
当未被回收对象的数量达到阈值时,LeakCanary 将 Java 堆 dump 到 Android 文件系统中的.hprof
文件(堆转储)中。转储堆会在短时间内冻结应用程序,在此期间 LeakCanary 显示以下 toast:
2.1.3、分析堆
LeakCanary 使用 Shark 来解析 .hprof 文件并在该堆转储中定位未被回收的对象。
对于每个未被回收对象,LeakCanary 会找到阻止该对象被 GC 垃圾回收的引用路径:它的 leak trace。
分析完成后,LeakCanary 会显示一个带有摘要的通知,并将结果打印在 Logcat中。请注意下面 4 个未被回收的对象是如何被分组为两种不同的泄漏项。LeakCanary 为每个 leak trace 创建一个签名,并将具有相同签名的泄漏项划分在一组,即由相同错误引起的泄漏。
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...
点击通知会启动一个提供更多详细信息的 Activity。稍后通过点击 LeakCanary 启动器图标再次返回它:
每行对应一组具有相同签名的泄漏项。LeakCanary 在应用程序第一次使用该签名触发泄漏时将一行标记为 New
。
点击泄漏项以打开其 leak trace显示详情。可以通过下拉菜单在不同的泄漏对象间切换。
泄漏签名 是导致泄漏的每个引用的串联哈希,即每个引用都显示有红色下划线:
当 leak trace以文本形式共享时,这些相同的可疑引用会带有下划线~~~
:
...
│
├─ com.example.leakcanary.LeakingSingleton class
│ Leaking: NO (a class is never leaking)
│ ↓ static LeakingSingleton.leakedViews
│ ~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[0]
│ ~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
...
在上面示例中,泄漏签名的计算方式为:
val leakSignature = sha1Hash(
"com.example.leakcanary.LeakingSingleton.leakedView" +
"java.util.ArrayList.elementData" +
"java.lang.Object[].[x]"
)
println(leakSignature)
// dbfa277d7e5624792e8b60bc950cd164190a11aa
2.1.4、对泄漏进行分类
LeakCanary 将它在 App 中发现的泄漏分为两类:Application Leaks 和 Library Leaks。
- Library Leaks是 App 中依赖的三方代码库中的已知错误引起的泄漏。此泄漏会直接影响到 App 的表现,但开发者无法直接在 App 中修复它,因此 LeakCanary 将其分离出来。
这两个类别在 Logcat中打印的结果中是分开的:
====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
│
...
LeakCanary 在其泄漏列表中标记为 Library Leak:
2.2、LeakCanary 使用
2.2.1、引入依赖
首先,需要将 leakcanary-android 依赖添加到项目的 app’s build.gradle 中:
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
因为 LeakCanary 有以下问题,所以通常只会使用在线下 debug 阶段,release 版本中不会引入 LeakCanary。
- 每次内存泄漏以后,都会生成并解析 hprof 文件,容易引起手机卡顿等问题
- 多次调用 GC,可能会对线上性能产生影响
- hprof文件较大,信息回捞成问题
然后,可过滤 Logcat 中的标签来确认 LeakCanary 在启动时是否成功运行:
LeakCanary: LeakCanary is running and ready to detect leaks
2.2.1.1、原理
LeakCanary在更新至2.x版本后,最大的一个不同就是不用在Application的onCreate中对其进行初始化处理了。
观察其Manifest.xml文件可见端倪,此处有一个ContentProvider注册。对应ContentProvider为:
ContentProvier 一般会在 Application 被创建之前被加载,LeakCanary 在其 onCreate() 方法中调用了 AppWatcher.manualInstall(application)
进行初始化。
这种方式的确是方便了开发者,但是仔细想想弊端还是很大的,如果所有第三方库都如此操作,开发者就没法控制应用启动时间了。现在很多APP为了用户体验对启动时间有严格限制,包括按需延迟初始化第三方库。
但在 LeakCanary 中,这个问题并不存在,因为它本身就是一个只在 debug 版本中使用的库,并不会对 release 版本有任何影响。(这里知道为什么只允许debugImplementation
了吧)
2.2.2、配置 LeakCanary
因为 LeakCanary 2.0 版本后完全使用 Kotlin 重写,只需引入依赖,不需要初始化代码,就能执行内存泄漏检测。
当然也可以在自定义 Application 的 onCreate方法对 LeakCanary 进行一些自定义配置:
class LeakApplication: Application() {
override fun onCreate() {
super.onCreate()
leakCanaryConfig()
}
private fun leakCanaryConfig() {
//App 处于前台时检测保留对象的阈值,默认是 5
LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
//自定义要检测的保留对象类型,默认监测 Activity,Fragment,FragmentViews 和 ViewModels
AppWatcher.config= AppWatcher.config.copy(watchFragmentViews = false)
//隐藏泄漏显示活动启动器图标,默认为 true
LeakCanary.showLeakDisplayActivityLauncherIcon(false)
}
}
2.2.3、检测内存泄漏
以下,举一例非静态内部类导致的内存泄漏,如何使用 LeakCanary 监控其异常,代码如下所示:
class LeakTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_leak_test)
val leakThread = LeakThread()
leakThread.start()
}
// LeakThread 定义为 LeakTestActivity 的内部类
inner class LeakThread : Thread() {
override fun run() {
super.run()
try {
//线程内耗时操作
sleep(6 * 60 * 1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
LeakTestActivity 存在内存泄漏,原因就是非静态内部类 LeakThread 持有外部类 LeakTestActivity 的引用,LeakThread 中做了耗时操作,导致 LeakTestActivity 无法被释放。
运行 App 程序,这时会在 Launch 界面生成一个名为 Leaks 的应用图标。接下来跳转到 App 的 LeakTestActivity 页面并不断地切换横竖屏,4 次切换后屏幕会弹出提示:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过 Notification 展示出来,如下图所示
Notification 中提示了 LeakTestActivity 发生了内存泄漏,有 4 个对象未被回收。点击 Notification 就可以进入内存泄漏详细页,除此之外也可以通过 Leaks 应用的列表界面进入,列表界面如下图所示
内存泄漏详细页如下图所示:
整个详情就是一个引用链:LeakTestActivity 的内部类 LeakThread 引用了 LeakThread 的 this$0
,this$0
的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的 LeakTestActivity 的实例,这将会导致 LeakTestActivity 无法被 GC,从而产生内存泄漏。
解决方法就是将 LeakThread 改为静态内部类。再次运行程序 LeakThread 就不会给出内存泄漏的提示了。
companion object {
class LeakThread : Thread() {
override fun run() {
super.run()
try {
sleep(6 * 60 * 1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
3、Fragment 和 Activity 的监听
ActivityRefWatcher中,注册Activity生命周期监听接口:registerActivityLifecycleCallbacks
,当Activity onDestroy()被调用时,将当前Activity加入内存泄漏监听队列。
public class MyApplication extends Application {
private static final String TAG = "Alvin application";
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
Log.d(TAG, "onActivityCreated: " + activity.getClass().getName());
new FragmentWatcher().watchFragments((AppCompatActivity) activity);
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
// Log.d(TAG, "onActivityStarted: " + activity.getPackageName());
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
// Log.d(TAG, "onActivityResumed: " + activity.getPackageName());
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
// Log.d(TAG, "onActivityPaused: " + activity.getPackageName());
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
// Log.d(TAG, "onActivityStopped: " + activity.getPackageName());
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
// Log.d(TAG, "onActivitySaveInstanceState: " + activity.getPackageName());
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
Log.d(TAG, "onActivityDestroyed: " + activity.getClass().getName());
// activity 需要回收了 watch(activity);
}
});
}
}
同理,Fragment通过FragmentManager注册FragmentLifecycleCallbacks
public class FragmentWatcher {
private static final String TAG = " alvin FragmentWatcher";
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable Bundle savedInstanceState) {
super.onFragmentActivityCreated(fm, f, savedInstanceState);
Log.i(TAG, "onFragmentActivityCreated: " + f.getClass().getName());
}
@Override
public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
super.onFragmentViewDestroyed(fm, f);
Log.i(TAG, "onFragmentViewDestroyed: " + f.getClass().getName());
//watch(view);
}
@Override
public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
super.onFragmentDestroyed(fm, f);
Log.i(TAG, "onFragmentDestroyed: " + f.getClass().getName());
// watch(f);
}
};
void watchFragments(AppCompatActivity activity) {
FragmentManager fragmentManager = activity.getSupportFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks,true);
}
final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
};
}
4、源码分析
4.1、ReferenceQueue说明
Java四大引用
- 强引用: 绝不回收
- 软引用: 内存不足才回收
- 弱引用: 碰到就回收
- 虚引用: 等价于没有引用,只是用来标识下指向的对象是否被回收。
我们可以为弱引用:(要监听的对象,例如activity)指定一个引用队列,当弱引用指向的对象被回收时,此弱引用就会被添加到这个队列中,我们可以通过判断这个队列中有没有这个弱引用
,来判断该弱引用指向的对象是否被回收了。
// 创建一个引用队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
private void test() {
// 创建一个对象
Object obj = new Object();
// 创建一个弱引用,并指向这个对象,并且将引用队列传递给弱引用
WeakReference<Object> reference = new WeakReference(obj, queue);
// 打印出这个弱引用,为了跟gc之后queue里面的对比证明是同一个
System.out.println("这个弱引用是:" + reference);
// gc一次看看(毛用都没)
System.gc();
// 打印队列(应该是空)
printlnQueue("before");
// 先设置obj为null,obj可以被回收了
obj = null;
// 再进行gc,此时obj应该被回收了,那么queue里面应该有这个弱引用了
System.gc();
// 再打印队列
printlnQueue("after");
}
private void printlnQueue(String tag) {
System.out.print(tag);
Object obj;
// 循环打印引用队列
while ((obj = queue.poll()) != null) {
System.out.println(": " + obj);
}
System.out.println();
}
打印结果如下所示:
这个弱引用是:java.lang.ref.WeakReference@6e0be858
before
after: java.lang.ref.WeakReference@6e0be858
通过上述代码,
- 当
obj
不为null
时,进行gc
,发现queue
里面什么都没有; - 然后将
obj
置为null
之后,再次进行gc
,发现queue
里面有这个弱引用了,这就说明obj
已经被回收了
利用这个特性,我们就可以检测Activity 的内存泄漏,Activity在onDestroy()之后被销毁,那么我们如果利用弱引用来指向Activity,并为它指定一个引用队列,然后在**onDestroy()**之后,去查看引用队列里是否有该Activity对应的弱引用
,就能确定该Activity是否被回收了。
4.2、注册监听入口(第三节)
用Application
的registerActivityLifecycleCallbacks()
这个api,就可以检测所有Activity 的生命周期,然后在onActivityDestroyed(activity)
这个方法里去检测此activity对应的弱引用是否被放入引用队列,
- 如果被放入,说明此activity已经被回收了,
- 否则说明此activity发生了泄漏,此时就可以将相关信息打印出来。
注意:
activity的onDestroy()被调用了,只是说明该activity被销毁了,并不是说已经发生了gc,所以,必要的时候,我们需要手动调用下gc,来保证我们的内存泄漏检测逻辑一定是执行在gc之后,这样才能防止误报。
4.3、Watcher和Activity的监测时机
在这里AppWatcher.manualInstall(application)
调用的是InternalAppWatcher.install(application)
,并在其中进行初始化:
在这里,
- ①是判断是否是在主线程中调用,如果不是就会抛出异常;
- ②负责监听Activity的onDestory();
- ③负责监听Fragment的onDestory()。
这里,我们先来看下ActivityDestroyWatcher.install()
的源码:
可见application.registerActivityLifecycleCallbacks()
在这里是注册 Activity 生命周期监听,
而对应的lifecycleCallbacks
中,则是监听到 onDestroy()
之后,通过 objectWatcher监测 Activity。
如此,可以说,install()
方法中注册了 Activity 生命周期监听,在监听到 onDestroy()
时,调用 objectWatcher.watch()
开始监测 Activity
。
到这里了,可以发现,下一步的突破方法在watch()。我们来观察一下此方法:
在这里,
- ①removeWeaklyReachableObjects()是把gc前ReferenceQueue中的引用清除;
- ②KeyedWeakReference()是将activity等包装为弱引用,并于ReferenceQueue建立关联;
- ③5s之后进行检测(5秒内gc完成)。
要注意的是③中的checkRetainedExecutor
是传入参数,此处由InternalAppWatcher.kt
中的checkRetainedExecutor
做传入参数:
可见,5秒钟是这么来的。继续观察③中的moveToRetained
():
5秒时间内,清除所有弱引用对象,进行gc操作:
如果gc操作完成,则上述变量watchObjects肯定清空,则retainedRef必定为null,如果没有清空,则触发内存泄漏处理。(当然5秒内也不一定会触发gc,所以之后的内存泄漏处理会主动gc再判断一次)
还记得我们前面为Activity生成的key吗,当这个Activity被回收后,指向它的弱引用就会被放入引用队列queue中,所以当我们检测到queue中有这个引用时,就说明该Activity已经被回收了,就从watchObjects队列移除这个key。所以,当一个Activity被destroy之后,就先把它对应的key添加到watchObjects队列中,等到gc之后,再检测watchObjects这个队列,如果对应的key还在,就说明发生了内存泄漏。
4.4、Fragment的监测时机
在最开始的install()中,③处开始便是对Fragment的检测:
观察其FragmentDestroyWatcher.install()
流程会发现最终会看到AndroidOFragmentDestroyWatcher
:
最终在 onFragmentViewDestroyed()和 onFragmentDestroyed()中分别将 Fragment中的View 和 Fragment 加入 watchedObjects 里面等待检测。
4.5、ViewModel的检测时机
关于ViewModel的检测时机,则要关注ViewModelClearedWatcher
。ViewModelClearedWatcher
继承自ViewModel
,其本身是一个ViewModel
,且实现了onCleared()
,这个方法类似Activity的onDestroy()
,在ViewModel销毁的时候会执行。
当ViewModelClearedWatcher
创建的时候,通过反射
拿到宿主的mMap
,这样便得到宿主中所有的ViewModel
。当ViewModelClearedWatcher
被销毁时,会回调onCleared()
,这时会遍历宿主中所有的ViewModel
,执行reachabilityWatcher
的expectWeaklyReachable
方法,判断ViewModel是否都已经释放。
4.6、总结
-
1、使用
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堆栈信息并打印日志,否则没有发生内存泄漏,流程结束。
5、优缺陷
优点
- 针对 Android Activity 组件完全自动化的内存泄漏检查
- 可定制一些行为(dump 文件和 leak trace 对象的数量、分析结果的自定义处理等)
- 集成过程简单并且使用成本很低
- 友好的界面展示和通知
缺点
- 不适用于线上监测
- 无法检测申请大容量内存导致的 OOM 问题、Bitmap 内存未释放问题
6、简单实现leakcanary,简化版,没有dump
KeyWeakReference
/**
* 继承自 WeakReference,并且加入一个 key,用来通过可以 key可以查找到对应的 KeyWeakReference
* @param <T>
*/
public class KeyWeakReference<T> extends WeakReference<T> {
private String key;
private String name;
public KeyWeakReference(T referent, String key, String name) {
super(referent);
this.key = key;
this.name = name;
}
public KeyWeakReference(T referent, ReferenceQueue<? super T> q, String key, String name) {
super(referent, q);
this.key = key;
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("KeyWeakReference{");
sb.append("key='").append(key).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
Watcher
public class Watcher {
//观察列表
private HashMap<String, KeyWeakReference> watchedReferences = new HashMap<>();
//怀疑列表
private HashMap<String, KeyWeakReference> retainedReferences = new HashMap<>();
//引用队列,相当于一个监视器设备,所有需要监视的对象,盛放监视对象的容器 都与之关联
//当被监视的对象被gc回收后,对应的容器就会被加入到queue
private ReferenceQueue queue = new ReferenceQueue();
public Watcher() {
}
/**
* 清理观察列表和怀疑列表的引用容器
*/
private void removeWeaklyReachableReferences() {
System.out.println("清理列表...");
KeyWeakReference findRef = null;
do {
findRef = (KeyWeakReference) queue.poll();
//不为空说明对应的对象被gc回收了,那么可以把对应的容器从观察列表,怀疑列表移除
System.out.println("findRef = " + findRef);
if (findRef != null) {
System.out.println("打印对应的对象的key: " + findRef.getKey());
//根据key把观察列表中对应的容器移除
Reference removedRef = watchedReferences.remove(findRef.getKey());
//如果removedRef为空,那么有可能被放入到怀疑列表了
//那么尝试从怀疑列表中移除
if (removedRef == null) {
retainedReferences.remove(findRef.getKey());
}
}
} while (findRef != null);//把所有放到referenceQueue的引用容器找出来
}
/**
* 根据key把对应的容器加入到怀疑列表
*
* @param key
*/
private synchronized void moveToRetained(String key) {
System.out.println("加入到怀疑列表...");
//在加入怀疑列表之前,做一次清理工作
removeWeaklyReachableReferences();
//根据key从观察列表中去找盛放对象的容器,如果被找到,说明到目前为止key对应的对象还没被回收
KeyWeakReference retainedRef = watchedReferences.remove(key);
if (retainedRef != null) {
//把从观察列表中移除出来的对象加入到怀疑列表
retainedReferences.put(key, retainedRef);
}
}
public void watch(Object watchedReference, String referenceName) {
System.out.println("开始watch对象...");
//1.在没有被监视之前,先清理下观察列表和怀疑列表
removeWeaklyReachableReferences();
//2.为要监视的对象生成一个唯一的 uuid
//相当于把要监视的对象 和容器 与 引用队列建立联系
final String key = UUID.randomUUID().toString();
System.out.println("待监视对象的key: " + key);
//3.让 watchedReference与一个 KeyWeakReference建立一对一映射关系,并与引用队列 queue关联
KeyWeakReference reference = new KeyWeakReference(watchedReference, queue, key, "");
//4. 加入到观察列表
watchedReferences.put(key, reference);
//5.过5秒后去看是否还在观察列表,如果还在,则加入到怀疑列表
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
Utils.sleep(5000);
moveToRetained(key);
});
}
public HashMap<String, KeyWeakReference> getRetainedReferences() {
retainedReferences.forEach((key, keyWeakReference) -> {
System.out.println("key: " + key + " , obj: " + keyWeakReference.get() + " , keyWeakReference: " + keyWeakReference);
}
);
return retainedReferences;
}
}
Utils
public class Utils {
public static void sleep(long millis){
System.out.println("sleep: " + millis);
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void gc(){
System.out.println("执行gc...");
// 注意这里不是使用System.gc,因为它仅仅是通知系统在合适的时间进行一次垃圾回收操作,实际上并不保证一定执行
// System.gc()不会每次都进行垃圾收集。Runtime.gc()更有可能执行gc。
Runtime.getRuntime().gc();
sleep(100);
System.runFinalization();
}
}
leakcanaryTest
public static void main(String[] args) {
Watcher watcher = new Watcher();
Object obj = new Object();
System.out.println("obj: " + obj);
watcher.watch(obj, "");
Utils.sleep(500);
//释放对象
obj = null;
Utils.gc();
Utils.sleep(6000);
System.out.println("查看是否在怀疑列表:" + watcher.getRetainedReferences().size());
}
参考
1、面试官问我:如何使用LeakCanary排查Android中的内存泄露,看我如何用漫画装逼!
2、LeakCanary源码精简分析
3、LeakCanary原理分析
4、LeakCanary原理解析
5、LeakCanary 2.0 工作原理及使用详解