LeakCanary2.6版本原理分析-如何检测内存泄漏?

一、介绍

 LeakCanary是一款开源的内存泄漏检查工具,在项目中,可以使用它来检测Activity是否能够被GC及时回收。帮助我们提高APP的稳定性。

官网地址:https://square.github.io/leakcanary/getting_started/

二、使用

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

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

就这样,无需更改代码!

通过过滤Logcat中LeakCanary标签来确认LeakCanary在启动时正在运行:

D LeakCanary: LeakCanary is running and ready to detect leaks

LeakCanary自动检测以下对象的泄漏:

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

三、LeakCanary如何运作

一旦安装了LeakCanary,它就会通过4个步骤自动检测并报告内存泄漏:

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

1.检测残留物

LeakCanary可以挂接到Android生命周期中,以自动检测Activity和Fragment何时被破坏并应进行垃圾收集。这些被破坏的对象将传递给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 object
  retained

LeakCanary在转储堆之前等待保留对象的数量达到阈值,并显示带有最新数量的通知。

通知 图1. LeakCanary找到了4个保留的对象。

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

信息

默认阈值5保留的对象时,应用程序是可见的,和1保留对象时,应用程序是不可见的。如果看到保留对象通知,然后将应用程序置于后台(例如,通过按“主页”按钮),则阈值从5更改为1,LeakCanary在5秒钟内转储了堆。点击通知将强制LeakCanary立即转储堆。

2.转储堆

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

吐司 

图2. LeakCanary显示了在堆放时的Toast。

3.分析堆

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

完成 

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

对于每个保留的对象,LeakCanary会查找引用路径,以防止对该保留的对象进行垃圾回收:其泄漏跟踪。您将在下一部分中学习分析泄漏跟踪:修复内存泄漏

完成 

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

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

完成 

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

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

轻触通知将启动提供更多详细信息的活动。稍后点击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个已知的bug泄漏RD党的代码,你没有控制权。此泄漏正在影响您的应用程序,但是很遗憾,修复泄漏可能不在您的控制范围内,因此LeakCanary会将其分离出来。

Logcat中打印的结果中将这两类分开:

====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS

====================================
1 LIBRARY LEAK

...
┬───
│ GC Root: Local variable in native code
│
...

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

图书馆泄漏 

图10. LeakCanary发现了Library Leak。

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FrancisBingo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值