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. Python使用matplotlib绘制分组对比柱状图(bar plot)可视化时汉语(中文)标签显示成了框框□□、什么情况、我们有解决方案
  2. Collections Arrays你会用么?
  3. 简单记录一下fabric版本1.4的环境搭建,
  4. 数据中心运营商如何选择合适的蓄电池
  5. qc成果报告范例_质量引领创新 扬子江勇夺医药行业QC“十五连冠”
  6. 何时、何地应用何种窗函数?
  7. 如何优化 Java 性能? 1
  8. Atitit. .net c# web 跟客户端winform 的ui控件结构比较
  9. java 打牌游戏_java代码-----实现4个人打牌游戏的相关代码。线程
  10. JAVA经纬度距离计算并排序-Spatial4j+ForkJoin
  11. DayDayUp:2020,再见了,不平凡的一年,让我懂得了珍惜,让我明白了越努力越幸运
  12. EXCEL表格-整体加密和内容加密
  13. 金蝶kis修改服务器,金蝶kis 修改服务器地址
  14. 一个简单的多线程实现
  15. root cause java.lang.LinkageError: loader constraint violation: loader (instanc
  16. PHPStorm 显示自动换行
  17. 第一次冲刺--查看活动详情用户场景分析
  18. 提高企业竞争力,如何开展知识管理工作?
  19. pat乙级练习记录-1017
  20. 永磁同步电机矢量控制中的双闭环是什么意思_【百问百答】ST 电机控制实战问答合辑 | 连载之二...

热门文章

  1. Trace32手册-Linux Debugging Reference Card
  2. 花了一年时间开发出来的AutoCAD矢量字库编辑器
  3. 阿里云服务器部署(新手福利)
  4. 如何从外网访问本地服务器
  5. ps 2018 安装包以及pojie
  6. winrar介绍,下载(52pojie)
  7. 基于bootstrap的漂亮网站后台管理界面框架汇总
  8. matlab手写板,手写MNISTmatlab实现
  9. House of Force
  10. 让我们泪流满面的电影经典台词