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

news2024/11/24 6:49:09

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 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 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 处于前台时检测保留对象的阈值,默认是 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 引用了 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";
    @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 的内存泄漏,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;
    }

    @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 工作原理及使用详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hystrix断路器 (豪猪)-保险丝

一、概述 重点&#xff1a;能让服务的调用方&#xff0c;够快的知道被调方挂了&#xff01;不至于说让用户在等待。 Hystix 是 Netflix 开源的一个延迟和容错库&#xff0c;用于隔离访问远程服务、第三方库&#xff0c;防止出现级联失败&#xff08;雪崩&#xff09;。 雪崩&am…

自动驾驶行业观察之2023上海车展-----车企发展趋势(2)

自主品牌发展 比亚迪&#xff1a;展示3款新车&#xff0c;均于2023年年内上市 比亚迪在本次展会上推出了3款新车&#xff1a;宋L概念车&#xff08;王朝系列&#xff09;、驱逐舰07&#xff08;海洋系列&#xff09;、海鸥&#xff08;海洋系列&#xff09;。 • 宋L&#x…

FreeRTOS:任务的创建和删除

目录 一、函数xTaskCreate二、函数xTaskCreateStatic三、函数xTaskCreateRestricted四、函数vTaskDelete五、任务创建和删除&#xff08;动态&#xff09;代码5.1实验要求5.2程序代码 一、函数xTaskCreate 此函数用来创建一个任务&#xff0c;任务需要RAM来保存与任务有关的状…

Mysql数据库基本操作(增删改查)

目录 一、数据库的基本操作1.1 登录数据库1.2 创建数据库并进入数据库 二、查看数据库结构2.1 查看数据库信息2.2 查看数据库中包含的表及表结构2.3、常用的数据库类型 三、Mysql数据文件3.1 MYD文件3.2 MYI文件3.3 MyISAM存储引擎 四、SQL 语句1、 DDL数据定义语言1.1 删除指定…

〖Python网络爬虫实战㉒〗- 数据存储之数据库详解

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

Eureka、Zookeeper、Consul服务注册与发现

一、Eureka服务注册与发现 1.1 概念 Eureka 是 Netflix 公司开源的一个服务注册与发现的组件 。 Eureka 和其他 Netflix 公司的服务组件&#xff08;例如负载均衡、熔断器、网关等&#xff09; 一起&#xff0c;被 Spring Cloud 社区整合为Spring-Cloud-Netflix 模块。 Eure…

校招失败后,在小公司熬了 2 年终于进了百度,竭尽全力....

其实两年前校招的时候就往百度投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里这两年除了工作以外&#xff0c;也会坚持写博客&#xff0c;也因此结识了很多优秀的小伙伴&#xff0c;从他们身上学到了特别…

【JavaEE】TCP协议的十大原理保姆讲解(Transmission Control Protocol)

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 上一篇文章讲了UDP协议&#xff0c;那么这篇文章我来讲讲TCP协议&#xff0c;TCP协议相对UDP协议难一些&#xff0c;内容相对更多。 TCP&#xff0c;即Transmission Control Protocol&…

AArch32 AArch64 Registers map详细解析与索引

1、AArch32 Registers AArch32 系统寄存器索引。 例如第一个寄存器ACTLR点击后解析如下&#xff1a; 2、AArch64 Registers AArch64 系统寄存器索引。 3、AArch32 Operations AArch32 系统指令索引。 例如第一个寄存器ACTLR_EL1点击后解析如下&#xff1a; 4、AArch…

springboot+vue考研资讯平台(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的考研资讯平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&a…

干翻Mybatis源码系列之第六篇:Mybatis缓存内容概述

前言 一&#xff1a;后续Mybatis我们会研究那些内容&#xff1f; Mybatis核心运行源码分析&#xff08;前面系列文章已经探讨过&#xff09; Mybatis中缓存的使用 Mybatis与Spring集成 Mybatis 插件。 Mybatis的插件可以对Mybatis内核功能或者是业务功能进行拓展&#xff0c…

David Silver Lecture 3: planning by dynamic programming

1 Introduction 1.1 定义 定义&#xff1a; Dynamic: sequential or temporal component to the problem Programming: optimising a program: a policy 核心思想&#xff1a; 将复杂问题拆解成简单子问题 1.2 Requirements for dynamic programming Optimal substructure …

【SWAT水文模型】SWAT水文模型建立及应用第三期:基于世界土壤数据库HWSD建立土壤库(待更新)

SWAT水文模型建立及应用&#xff1a;土壤库建立 1 简介2 土壤数据下载2.1 数据下载方式2.1.1 世界土壤数据库HWSD数据2.1.2 中国土壤数据库 2.2 数据下载 3 土壤数据的准备3.1 SWAT土壤数据库参数3.2 土壤质地转化3.3 土壤参数的提取3.4 其他变量的提取3.5 土壤类型分布图的处理…

高级IO涉及的编程模型

目录 五种IO模型引入IO模型阻塞IO非阻塞IO信号驱动IOO多路转接异步IO IO重要概念同步通信 vs 异步通信阻塞 vs 非阻塞 其他高级IO 非阻塞IOfcntl基于fcntl将文件描述符设置为非阻塞轮询方式读取标准输入 I/O多路转接之select初识selectselect函数原型参数timeout取值fd_set结构…

React Native中防止滑动过程中误触

React Native中防止滑动过程中误触 在使用React Native开发的时&#xff0c;当我们快速滑动应用的时候&#xff0c;可能会出现误触&#xff0c;导致我们会点击到页面中的某一些点击事件&#xff0c;误触导致页面元素响应从而进行其他操作,表现出非常不好的用户体验。 一、问题…

Kafka安装以及入门基本命令操作

文章目录 1.单节点搭建1.1 下载安装包1.2 配置环境变量1.3 配置配置文件1.4 启动启动zookeeper启动kafka 1.5 创建启动脚本startKafka.sh 2.简单的使用2.1 创建topic2.2 查看topic2.3 producer生产数据2.4 consumer消费者拉取数据 1.单节点搭建 1.1 下载安装包 #解压 tar -xz…

Android framework的底层原理,再不看就真有可能被淘汰

前言 从事Android开发的人都知道&#xff0c;目前市面上有各种类型跨平台技术诞生&#xff0c;严重冲击了Android市场&#xff0c;越来越多的Android开发者不再做移动应用开发&#xff0c;而另一方面&#xff0c;系统开发由于其复杂的逻辑&#xff0c;形成了独有的核心竞争力&…

Java 基础进阶篇(一)—— String 与 StringBuilder

文章目录 一、String 类概述二、String 创建对象的方式2.1 创建对象的两种方式2.2 面试&#xff1a;两种方式的区别 ★2.3 常见面试题 ★ 三、String 类常用方法3.1 字符串内容比较3.2 常用 API&#xff1a;遍历、截取、替换、分割 四、StringBuilder 字符串操作类4.1 构造器4.…

JavaScript 数据类型判断

&#xff08;生活的道路一旦选定&#xff0c;就要勇敢地走到底&#xff0c;决不回头。——左拉&#xff09; typeof typeof是在javascript编码过程中最常用的判断数据类型的方法&#xff0c;常用来判断数字&#xff0c;字符串&#xff0c;布尔值和undefind console.log(typeo…

ROS Noetic版本 rosdep找不到命令 不能使用的解决方法

使用rosdep指令来安装开源包所需的依赖是很方便的&#xff0c;本文主要介绍ROS Noetic版本中使用rosdep&#xff0c;报错找不到命令 &#xff0c;rosdep不能使用的解决方法。 rosdep&#xff1a;找不到命令 Command rosdep not found, but can be installed with:sudo apt ins…