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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐