LeakCanary github地址:LeakCanary

要使用 LeakCanary,请将leakcanary-android依赖项添加到应用程序的build.gradle文件中:

dependencies {// debugImplementation because LeakCanary should only run in debug builds.debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}

在项目中加入LeakCanary之后就可以检测项目的内存泄露了,LeakCanary通过过滤 Logcat 中的标签来确认 LeakCanary 在启动时运行

LeakCanary 的工作原理

安装 LeakCanary 后,它会自动检测并报告内存泄漏,分 4 个步骤:

  1. 检测保留对象。
  2. 转储堆。
  3. 分析堆。
  4. 对泄漏进行分类。

1.检测保留对象¶

LeakCanary 挂钩到 Android 生命周期,以自动检测活动和片段何时被销毁并且应该被垃圾收集。这些被破坏的对象被传递给一个ObjectWatcher,它持有对它们的弱引用。LeakCanary 自动检测以下对象的泄漏:

  • 销毁的Activity实例
  • 销毁的Fragment实例
  • 被破坏的片段View实例
  • 清除ViewModel实例

您可以查看不再需要的任何对象,例如分离的视图或销毁的演示者:

AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")

如果在等待 5 秒并运行垃圾回收后,持有的弱引用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 在转储堆之前等待保留对象的计数达到阈值,并显示具有最新计数的通知。

图 1. LeakCanary 发现了 4 个保留对象。

D LeakCanary: Rescheduling check for retained objects in 2000ms because foundonly 4 retained objects (< 5 while app visible)

信息

应用可见时默认阈值为5 个保留对象,应用不可见默认阈值为1 个保留对象。如果您看到保留对象通知,然后将应用程序置于后台(例如通过按下 Home 按钮),则阈值从 5 变为 1,并且 LeakCanary 会在 5 秒内转储堆。点击通知会强制 LeakCanary 立即转储堆。

2. 转储堆¶

当保留对象的数量达到阈值时,LeakCanary 将 Java 堆转储到存储在 Android 文件系统上的.hprof文件(堆转储)中(请参阅LeakCanary 在哪里存储堆转储?)。转储堆会在短时间内冻结应用程序,在此期间 LeakCanary 显示以下 toast:

图 2. LeakCanary在转储堆时显示吐司。

3. 分析堆¶

LeakCanary.hprof使用Shark解析文件并在该堆转储中定位保留的对象。

图 3. LeakCanary 在堆转储中找到保留对象。

对于每个保留对象,LeakCanary 会找到阻止该保留对象被垃圾收集的引用路径:它的泄漏跟踪。您将在下一节中学习分析泄漏跟踪:修复内存泄漏。

图 4. LeakCanary 计算每个保留对象的泄漏跟踪。

分析完成后,LeakCanary 会显示一个带有摘要的通知,并将结果打印在Logcat中。请注意下面4 个保留对象如何分组为2 个不同的泄漏。LeakCanary为每个泄漏跟踪创建一个签名,并将具有相同签名的泄漏分组在一起,即由相同错误引起的泄漏。

图 5. 4 个泄漏痕迹变成了 2 个不同的泄漏特征。

====================================
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
│
...

点击通知会启动一个提供更多详细信息的活动。稍后通过点击 LeakCanary 启动器图标再次返回它:

图 6. LeakCanary 为其安装的每个应用程序添加一个启动器图标。

每行对应一组具有相同签名的泄漏。LeakCanary在应用程序第一次使用该签名触发泄漏时将一行标记为新。

图 7. 4 个泄漏分组为 2 行,一个用于每个不同的泄漏签名。

点击泄漏以打开带有泄漏痕迹的屏幕。您可以通过下拉菜单在保留对象及其泄漏跟踪之间切换。

图 8.显示按共同泄漏特征分组的 3 个泄漏的屏幕。

泄漏签名是每个被怀疑导致泄漏的引用串联哈希,即每个引用都显示有红色下划线

图 9.带有 3 个可疑引用的泄漏跟踪。

~~~当泄漏跟踪以文本形式共享时,这些相同的可疑引用会带有下划线:

...
│
├─ 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

4. 对泄漏进行分类¶

LeakCanary 将它在您的应用中发现的泄漏分为两类:应用程序泄漏库泄漏库泄漏是由您无法控制的 3 rd方代码中的已知错误引起的泄漏。此泄漏正在影响您的应用程序,但不幸的是,修复它可能不在您的控制范围内,因此 LeakCanary 将其分离出来。

这两个类别在Logcat中打印的结果中是分开的:

====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS====================================
1 LIBRARY LEAK...
┬───
│ GC Root: Local variable in native code
│
...

LeakCanary 在其泄漏列表中将一行标记为库泄漏:

图 10. LeakCanary 发现了库泄漏。

LeakCanary 附带一个已知泄漏的数据库,它通过对引用名称的模式匹配来识别它。例如:

Leak pattern: instance field android.app.Activity$1#this$0
Description: Android Q added a new IRequestFinishCallback$Stub class [...]
┬───
│ GC Root: Global variable in native code
│
├─ android.app.Activity$1 instance
│    Leaking: UNKNOWN
│    Anonymous subclass of android.app.IRequestFinishCallback$Stub
│    ↓ Activity$1.this$0
│                 ~~~~~~
╰→ com.example.MainActivity instance

内存泄漏是一种编程错误,它会导致应用程序保留对不再需要的对象的引用。在代码的某处,有一个应该被清除但没有被清除的引用。

请按照以下 4 个步骤修复内存泄漏:

  1. 找到泄漏痕迹。
  2. 缩小可疑参考范围。
  3. 找到导致泄漏的参考。
  4. 修复泄漏。

LeakCanary 帮助您完成前两个步骤。最后两个步骤由您决定!

1.查找泄漏痕迹¶

泄漏跟踪是从垃圾收集根到保留对象的最佳强引用路径的较短名称,即在内存中持有对象的引用路径,因此防止它被垃圾收集。

例如,让我们在静态字段中存储一个辅助单例:

class Helper {}class Utils {public static Helper helper = new Helper();
}

让我们告诉 LeakCanary 单例实例应该被垃圾回收:

AppWatcher.objectWatcher.watch(Utils.helper)

该单例的泄漏跟踪如下所示:

┬───
│ GC Root: Local variable in native code
│
├─ dalvik.system.PathClassLoader instance
│    ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│    ↓ Object[].[43]
├─ com.example.Utils class
│    ↓ static Utils.helper
╰→ java.example.Helper

让我们分解吧!在顶部,PathClassLoader实例由垃圾收集 (GC) 根持有,更具体地说,是本机代码中的局部变量。GC 根是始终可访问的特殊对象,即它们不能被垃圾回收。有 4 种主要类型的 GC 根:

  • 局部变量,属于线程的栈。
  • 活动 Java 线程的实例。
  • 系统类,永远不会卸载。
  • 本机引用,由本机代码控制。
┬───
│ GC Root: Local variable in native code
│
├─ dalvik.system.PathClassLoader instance

以 开头的行├─表示 Java 对象(类、对象数组或实例),以 开头的行│ ↓表示对下一行 Java 对象的引用。

PathClassLoader有一个runtimeInternalObjects字段是对以下数组的引用Object

├─ dalvik.system.PathClassLoader instance
│    ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array

该数组中位置 43 处的元素是对该类Object的引用。Utils

├─ java.lang.Object[] array
│    ↓ Object[].[43]
├─ com.example.Utils class

以 开头的行╰→表示泄漏对象,即传递给AppWatcher.objectWatcher.watch()的对象。

该类Utils有一个静态helper字段,它是对泄漏对象的引用,它是 Helper 单例实例:

├─ com.example.Utils class
│    ↓ static Utils.helper
╰→ java.example.Helper instance

2. 缩小可疑参考范围¶

泄漏跟踪是引用的路径。最初,该路径中的所有引用都被怀疑导致泄漏,但 LeakCanary 可以自动缩小可疑引用的范围。要了解这意味着什么,让我们手动完成该过程。

这是一个糟糕的 Android 代码示例:

class ExampleApplication : Application() {val leakedViews = mutableListOf<View>()
}class MainActivity : Activity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.main_activity)val textView = findViewById<View>(R.id.helper_text)val app = application as ExampleApplication// This creates a leak, What a Terrible Failure!app.leakedViews.add(textView)}
}

LeakCanary 会生成如下所示的泄漏跟踪:

┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│    ↓ ExampleApplication.leakedViews
├─ java.util.ArrayList instance
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    ↓ Object[].[0]
├─ android.widget.TextView instance
│    ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance

以下是如何读取泄漏痕迹:

该类FontsContract是一个系统类(参见参考资料GC Root: System class),并且有一个sContext静态字段,该字段引用一个ExampleApplication实例,该实例具有一个leakedViews字段,该字段引用一个ArrayList实例,该实例引用一个数组(支持数组列表实现的数组),该数组具有一个引用 a 的元素,该元素TextView具有一个mContext字段它引用了MainActivity.

LeakCanary 使用 ~ ~ 下划线突出显示所有可能导致此泄漏的引用。最初,所有引用都是可疑的:

┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    ↓ static FontsContract.sContext
│                           ~~~~~~~~
├─ com.example.leakcanary.ExampleApplication instance
│    Leaking: NO (Application is a singleton)
│    ↓ ExampleApplication.leakedViews
│                         ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    ↓ TextView.mContext
│               ~~~~~~~~
╰→ com.example.leakcanary.MainActivity instance

然后,LeakCanary对泄漏跟踪中对象的状态生命周期进行推断。在 Android 应用程序中,Application实例是一个永远不会被垃圾回收的单例,因此它永远不会泄漏 ( Leaking: NO (Application is a singleton))。由此,LeakCanary 得出结论,泄漏不是由FontsContract.sContext(删除相应的~~~)引起的。这是更新的泄漏跟踪:

┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│    Leaking: NO (Application is a singleton)
│    ↓ ExampleApplication.leakedViews
│                         ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    ↓ TextView.mContext
│               ~~~~~~~~
╰→ com.example.leakcanary.MainActivity instance

实例通过它的字段TextView引用被破坏的实例。视图不应在其上下文的生命周期中存活,因此 LeakCanary 知道此实例正在泄漏 ( ),因此泄漏不是由(删除对应的) 引起的。这是更新的泄漏跟踪:MainActivitymContextTextViewLeaking: YES (View.mContext references a destroyed activity)TextView.mContext~~~

┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│    Leaking: NO (Application is a singleton)
│    ↓ ExampleApplication.leakedViews
│                         ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance

总而言之,LeakCanary 检查泄漏跟踪中对象的状态以确定这些对象是否正在泄漏 ( Leaking: YESvs Leaking: NO),并利用该信息来缩小可疑引用的范围。您可以提供自定义ObjectInspector实现来改进 LeakCanary 在您的代码库中的工作方式(请参阅识别泄漏对象和标记对象)。

3.找到导致泄漏的参考¶

在前面的示例中,LeakCanary 缩小了对和的可疑引用ExampleApplication.leakedViewsArrayList.elementDataObject[].[0]

┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│    Leaking: NO (Application is a singleton)
│    ↓ ExampleApplication.leakedViews
│                         ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance

ArrayList.elementDataObject[].[0]是 的实现细节ArrayList,并且实现中不太可能存在错误ArrayList,因此导致泄漏的引用是唯一剩余的引用:ExampleApplication.leakedViews.

4.修复泄漏¶

一旦找到导致泄漏的引用,您需要弄清楚该引用是关于什么的,何时应该清除它以及为什么没有清除。

无法通过用弱引用替换强引用来修复内存泄漏。在尝试快速解决内存问题时,这是一种常见的解决方案,但它永远不会奏效。导致引用保留时间超过必要的错误仍然存​​在。最重要的是,它会产生更多的错误,因为一些对象现在将比它们应该更快地被垃圾收集。它也使代码更难维护。

LeakCanary 使用相关推荐

  1. android 监测内存泄漏工具,LeakCanary:Android内存泄漏检测工具

    LeakCanary A memory leak detection library for Android and Java. "A small leak will sink a grea ...

  2. LeakCanary 源码解析

    LeakCanary 是什么? LeakCanary是Square公司基于MAT开源的一个工具,用来检测Android App中的内存泄露问题.官方地址:https://github.com/squa ...

  3. LeakCanary(一)使用篇

    图中的demo用于制造内存泄漏的情况,除非Docker类的classLoader被回收,不然static Box container对应的内存不会被回收. 相关链接:(前一篇是官方的中文使用文档,后一 ...

  4. 安卓 内存泄漏检测工具 LeakCanary 使用

    韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 配置  build.gradle dependencies {debugCompile 'com ...

  5. 使用LeakCanary遇到的问题 就是不弹出来

    今天楼主遇到引用LeakCanary时代码跟官网一样但是就不弹出来.楼主新建项目就可以正常使用.楼主郁闷半天,现在终于整出来了. 楼主主工程app引用module为thirdParty,本想为了整洁三 ...

  6. android内存泄漏原因分析,Android Studio3.6的内存泄漏检测功能 VS LeakCanary

    2020年2月,谷歌发布了Android Studio 3.6版.它包括一个新的"内存泄漏检测"功能.这是否意味着我们不再需要流行的内存泄漏检测库"Leak Canary ...

  7. Android开源框架——内存泄漏检测工具 LeakCanary

    开源地址:https://github.com/square/leakcanary FAQ : https://github.com/square/leakcanary/wiki/FAQ 配置 bui ...

  8. LeakCanary——消除Android中的内存泄露

    2019独角兽企业重金招聘Python工程师标准>>> ##LeakCanary ####简介 LeakCanary是Square公司最近公布的开源项目,旨在消除Android中的内 ...

  9. LeakCanary: 让内存泄露无所遁形

    LeakCanary: 让内存泄露无所遁形 09 May 2015 本文为LeakCanary: Detect all memory leaks!的翻译.原文在: https://corner.squ ...

  10. LeakCanary 源码分析

    1. 前言 LeakCanary 是由 Square 开发的一款内存泄露检测工具.相比与用 IDE dump memory 的繁琐,它以轻便的日志被广大开发者所喜爱.让我们看看它是如何实现的吧. ps ...

最新文章

  1. 团队-石头剪刀布-模块测试过程
  2. .NET Core开发日志——Runtime IDentifier
  3. Docker 精通之常用命令
  4. 小米10pro第二个摄像头下面_小米10至尊纪念版、小米10 Pro对比评测:至尊版“至尊”在哪里?...
  5. 是否同一棵二叉搜索树
  6. 怎么更改Windows11鼠标指针大小和样式
  7. Python学习笔记——条件分支和while循环
  8. 生成chm文档工具- Sandcastle -摘自网络
  9. Python玩转简书钻,简述钻是否对文章权重有影响?结果确实有!
  10. Hexo + Github搭建自己的博客
  11. 教你一行代码解决 Git报错 fatal refusing to merge unrelated histories
  12. vue实例的参数说明
  13. jquery,Ajax判断验证码是否正确,不正确阻止提交
  14. 戴尔电脑硬件自检教程
  15. 数学之美系列好文,强推
  16. 数学传奇3——神话的破灭
  17. vbscript下载文件(使用https绕过无效的证书错误)
  18. 为啥干不过苹果?某手机老总一语道破心中所想,赚钱才是第一位的
  19. 如何把语音转文字转换
  20. MATLAB实现图像灰度直方图

热门文章

  1. consul命令行查看服务_Consul 命令行最全文档
  2. python毕业设计作品基于django框架校园网站系统毕设成品(1)开发概要
  3. 刚走上工作岗位的程序员——如何看待业务和技术
  4. python rolling regression. 使用 Python 实现滚动回归
  5. linux下的tar压缩解压缩命令详解,Linux下的tar压缩解压缩命令详解
  6. 浏览器与解释器【转】
  7. Python3爬取前程无忧招聘数据教程
  8. DD-WRT封杀P2P
  9. Oracle数据库解决NULL值不走B树索引
  10. QQ已删好友查询手机版