Android:LeakCanary原理的简单理解和使用

news/2025/2/21 5:32:39/

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 以自动检测 ActivitisFragments 何时被 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 objectretained

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 LEAKSDisplaying 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 LeaksLibrary 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 处于前台时检测保留对象的阈值,默认是 5LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)//自定义要检测的保留对象类型,默认监测 Activity,Fragment,FragmentViews 和 ViewModelsAppWatcher.config= AppWatcher.config.copy(watchFragmentViews = false)//隐藏泄漏显示活动启动器图标,默认为 trueLeakCanary.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 引用了 LeakThreadthis$0this$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";@Overridepublic void onCreate() {super.onCreate();registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {Log.d(TAG, "onActivityCreated: " + activity.getClass().getName());new FragmentWatcher().watchFragments((AppCompatActivity) activity);}@Overridepublic void onActivityStarted(@NonNull Activity activity) {
//                Log.d(TAG, "onActivityStarted: " + activity.getPackageName());}@Overridepublic void onActivityResumed(@NonNull Activity activity) {
//                Log.d(TAG, "onActivityResumed: " + activity.getPackageName());}@Overridepublic void onActivityPaused(@NonNull Activity activity) {
//                Log.d(TAG, "onActivityPaused: " + activity.getPackageName());}@Overridepublic void onActivityStopped(@NonNull Activity activity) {
//                Log.d(TAG, "onActivityStopped: " + activity.getPackageName());}@Overridepublic void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
//                Log.d(TAG, "onActivitySaveInstanceState: " + activity.getPackageName());}@Overridepublic 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() {@Overridepublic void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable Bundle savedInstanceState) {super.onFragmentActivityCreated(fm, f, savedInstanceState);Log.i(TAG, "onFragmentActivityCreated: " + f.getClass().getName());}@Overridepublic void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {super.onFragmentViewDestroyed(fm, f);Log.i(TAG, "onFragmentViewDestroyed: " + f.getClass().getName());//watch(view);}@Overridepublic 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() {@Overridepublic void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {}@Overridepublic void onActivityStarted(@NonNull Activity activity) {}@Overridepublic void onActivityResumed(@NonNull Activity activity) {}@Overridepublic void onActivityPaused(@NonNull Activity activity) {}@Overridepublic void onActivityStopped(@NonNull Activity activity) {}@Overridepublic void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {}@Overridepublic 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 的内存泄漏,ActivityonDestroy()之后被销毁,那么我们如果利用弱引用来指向Activity,并为它指定一个引用队列,然后在**onDestroy()**之后,去查看引用队列里是否有该Activity对应的弱引用,就能确定该Activity是否被回收了。

4.2、注册监听入口(第三节)

Application的registerActivityLifecycleCallbacks()这个api,就可以检测所有Activity 的生命周期,然后在onActivityDestroyed(activity)这个方法里去检测此activity对应的弱引用是否被放入引用队列,

  • 如果被放入,说明此activity已经被回收了,
  • 否则说明此activity发生了泄漏,此时就可以将相关信息打印出来。

注意:

activityonDestroy()被调用了,只是说明该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。所以,当一个Activitydestroy之后,就先把它对应的key添加到watchObjects队列中,等到gc之后,再检测watchObjects这个队列,如果对应的key还在,就说明发生了内存泄漏。

4.4、Fragment的监测时机

在这里插入图片描述
在最开始的install()中,③处开始便是对Fragment的检测:
在这里插入图片描述
观察其FragmentDestroyWatcher.install()流程会发现最终会看到AndroidOFragmentDestroyWatcher
在这里插入图片描述
最终在 onFragmentViewDestroyed()和 onFragmentDestroyed()中分别将 Fragment中的ViewFragment 加入 watchedObjects 里面等待检测。

4.5、ViewModel的检测时机

关于ViewModel的检测时机,则要关注ViewModelClearedWatcherViewModelClearedWatcher继承自ViewModel,其本身是一个ViewModel,且实现了onCleared(),这个方法类似Activity的onDestroy(),在ViewModel销毁的时候会执行。
在这里插入图片描述
ViewModelClearedWatcher创建的时候,通过反射拿到宿主的mMap,这样便得到宿主中所有的ViewModel。当ViewModelClearedWatcher被销毁时,会回调onCleared(),这时会遍历宿主中所有的ViewModel,执行reachabilityWatcherexpectWeaklyReachable方法,判断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;}@Overridepublic 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回收后,对应的容器就会被加入到queueprivate 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 工作原理及使用详解


http://www.ppmy.cn/news/58803.html

相关文章

面试题30天打卡-day18

1、什么是单例模式&#xff1f;使用单例模式有什么好处&#xff1f;有哪些常用的单例模式实现方式&#xff1f;各自的应用场景是什么&#xff1f;请你举例说明哪些地方用到了单例模式&#xff1f; 单例是一种设计模式&#xff0c;应用该模式的类只会生成一个实例 &#xff0c;…

软考——数据结构,算法基础,程序设计语言,法律法规,多媒体基础

数据结构与算法基础 数组与矩阵线性表广义表树与二叉树图排序与查找算法基础及常见算法 数组 稀疏矩阵 直接把&#xff08;0&#xff0c;0&#xff09;带入&#xff0c;排除B&#xff0c;C 将&#xff08;1&#xff0c;1&#xff09;带入&#xff0c;排除D&#xff0c; 最终…

unityt光线透射目标

介绍 在Unity中&#xff0c;光线透射目标通常指的是在场景中放置的一些物体&#xff0c;用于模拟光线从一个物体透过到另一个物体的效果。canvas子物体组件中&#xff0c;勾不勾选“光线透射目标”有什么区别&#xff1f; 方法 在Canvas子物体组件中勾选“光线透射目标”时&…

Linux信号:SIGCHLD信号和僵尸进程

1. SIGCHLD信号产生条件&#xff1a; &#xff08;1&#xff09;子进程终止&#xff1b; &#xff08;2&#xff09;子进程收到SIGSTOP信号被暂停&#xff1b; &#xff08;3&#xff09;子进程处于暂停状态&#xff0c;收到SIGCONT信号被唤醒。 2. 捕捉SIGCHLD&#xff0c;避免…

ACM实训-分治算法(二分,递归分治,归并排序)

A (1). 金块问题 &#xff08;递归&#xff0c;分治&#xff09; #include<bits/stdc.h> using namespace std;int n; int a[100001];int F_mx(int l,int r){if(lr)return a[l];if(r-l1)return max(a[l],a[r]);int mid(lr)/2;int l_mxF_mx(l,mid);//向左查找int r_mxF_m…

【C++11】多线程+IO流

目录 一、C11线程库 1、每个线程都有独立的栈空间 2、加锁的位置 3、CSA操作 4、C的类模板atomic&#xff08;原子操作&#xff09; 5、lock_guard&#xff08;RAII风格的锁&#xff09;/unique_lock&#xff08;可随时释放锁&#xff09; 6、条件变量&#xff08;用于互…

Photoshop如何使用蒙版之实例演示?

文章目录 0.引言1.给单调的天空添加蓝天白云2.清除头发边缘的杂色3.制作景深效果4.制作枯荣共存的树5.制作双重曝光肖像 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对PS进行了学习&#xff0c;本文通过《Photoshop2021入门教程》及其配套素材结合网上相关资料进行…

Linux必会100个命令(六十)curl

在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具&#xff0c;可以说是一款很强大的http命令行工具。它支持文件的上传和下载&#xff0c;是综合传输工具。 curl选项比较多&#xff0c;使用man curl或者curl -h获取帮助信息。 -a/--append …