内存分析工具LeakCanary是如何工作的

LeakCanary通过Hook Android生命周期检测内存泄漏,当对象被销毁且无法被垃圾回收时,它导出堆文件并使用Shark分析。LeakCanary将泄漏分为Application Leaks和Library Leaks,并提供详细的泄漏引用路径。通过识别保留对象和分析堆信息,LeakCanary帮助开发者定位和解决内存问题。
摘要由CSDN通过智能技术生成

一旦LeakCanary被安装,它自动检测和报告内存泄漏,分4步:

  1. 检测保留下来的对象;
  2. 导出堆信息;
  3. 分析堆信息;
  4. 对内存泄漏进行分类;

目录

 

1.检测保留下来的对象

2.导出堆文件

3.分析堆文件

4.对内存泄漏进行分类

5.其他


1.检测保留下来的对象

LeakCanary通过Hook(劫持)Android生命周期去自动检测内存泄漏问题,当Activity和Fragment被销毁并且执行垃圾回收的时候;这些被销毁的对象被传递给ObjectWatcher(持有这些销毁的对象的弱引用);LeakCanary自动检测如下对象的内存泄漏:

a.已销毁的Activity实例;

b.已销毁的Fragment实例;

c.已销毁的片段View实例;

d.已经清除的ViewModel实例;

可以检测任何不在需要的对象,例如一个被移除的View或者一个销毁的Presenter:

AppWatcher.INSTANCE.getObjectWatcher().watch(textView2, "View was detached");

如果持有弱引用销毁对象的ObjectWatcher在等候5秒并且运行垃圾回收不能被清除,被观察的销毁对象可能被保留,存在潜在的内存泄漏问题;

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一直等候被保留未销毁的对象数量达到阀值(5)再导出heap堆hprof文件,并在通知栏显示最新未销毁对象的数量;

notification

通知提示有4个未销毁的对象被保留,点击通知可以导出heap堆文件;

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

注意:

默认阈值为应用程序可见时5个保留对象,应用程序不可见时1个保留对象。如果您看到retained objects通知,然后将应用程序置于后台(例如按Home按钮),那么阈值将从5更改为1,LeakCanary将在5秒内导出堆文件。点击通知会强制LeakCanary立即导出堆文件。

2.导出堆文件

当未销毁对象被保留达到阀值,LeakCanary导出Java的堆信息存储到hprof文件;导出heap堆文件会短暂冻结APP,在导出堆文件时会有如下通知:

toast

默认存储堆文件在app文件夹下的leakcanary目录下,如果设置android.permission.WRITE_EXTERNAL_STORAGE权限并授权此权限,则堆文件存储在SD卡的Download/leakcanary-com.example目录下,com.example是app的包名;

3.分析堆文件

Shark: Smart Heap Analysis Reports for Kotlin;

Shark是为LeakCanary 2提供功能强大的堆分析器。它是一个Kotlin独立堆分析库,以低内存占用率高速运行。

Shark被支持如下功能:

a.Shark Hprof:读取和写入Hprof文件中的记录。

b.Shark Graph:导航堆对象图。

c.Shark:生成堆分析报告。

d.Shark Android:Android启发式生成定制的堆分析报告。

e.Shark CLI:分析安装在连接到桌面的Android设备上的可调试应用程序堆。输出与LeakCanary的输出类似,只是您不必将LeakCanary依赖项添加到应用程序中。

LeakCanary:建在上面。它会自动监视被销毁的Activity和Fragment,触发堆存储,运行Shark Android,然后显示结果。

06-27 15:19:38.515 9186-9224/fan.fragmentdemo D/LeakCanary: Removing 1 heap dumps
06-27 15:19:41.523 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: PARSING_HEAP_DUMP
06-27 15:19:43.267 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: EXTRACTING_METADATA
06-27 15:19:43.469 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: FINDING_RETAINED_OBJECTS
06-27 15:19:43.994 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: FINDING_PATHS_TO_RETAINED_OBJECTS
06-27 15:19:44.500 9186-9223/fan.fragmentdemo D/LeakCanary: Setting up flushing for Thread[IntentService[HeapAnalyzerService],5,main]
06-27 15:19:47.737 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: FINDING_DOMINATORS
06-27 15:19:54.663 9186-9494/fan.fragmentdemo D/LeakCanary: Found 2 retained objects
06-27 15:19:54.663 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: COMPUTING_NATIVE_RETAINED_SIZE
06-27 15:19:55.480 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: COMPUTING_RETAINED_SIZE
06-27 15:19:55.554 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: BUILDING_LEAK_TRACES
06-27 15:19:55.558 9186-9494/fan.fragmentdemo D/LeakCanary: Found 2 paths to retained objects, down to 1 after removing duplicated paths
06-27 15:19:55.720 9186-9494/fan.fragmentdemo D/LeakCanary: Analysis in progress, working on: REPORTING_HEAP_ANALYSIS
06-27 15:19:55.737 9186-9494/fan.fragmentdemo D/LeakCanary: ====================================
                                                            HEAP ANALYSIS RESULT
                                                            ====================================
                                                            1 APPLICATION LEAKS
                                                            
                                                            References underlined with "~~~" are likely causes.
                                                            Learn more at https://squ.re/leaks.
                                                            
                                                            30451 bytes retained by leaking objects
                                                            Signature: f3466687f84b8cdd14a9862dcc5b72a7115e352b
                                                            ┬───
                                                            │ GC Root: System class
                                                            │
                                                            ├─ fan.fragmentdemo.MemoryTestActivity class
                                                            │    Leaking: NO (a class is never leaking)
                                                            │    ↓ static MemoryTestActivity.textView2
                                                            │                                ~~~~~~~~~
                                                            ╰→ android.support.v7.widget.AppCompatTextView instance
                                                            ​     Leaking: YES (ObjectWatcher was watching this because View was detached and View.mContext references a destroyed activity)
                                                            ​     key = 0f1c40a8-d5be-4253-ab5c-fdec9e64c65d
                                                            ​     watchDurationMillis = 15965
                                                            ​     retainedDurationMillis = 10964
                                                            ​     mContext instance of fan.fragmentdemo.MemoryTestActivity with mDestroyed = true
                                                            ​     View#mParent is set
                                                            ​     View#mAttachInfo is null (view detached)
                                                            ​     View.mID = R.id.textView2
                                                            ​     View.mWindowAttachCount = 1
                                                            ====================================
                                                            0 LIBRARY LEAKS
                                                            
                                                            A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
                                                            See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
                                                            ====================================
                                                            METADATA
                                                            
                                                            Please include this in bug reports and Stack Overflow questions.
                                                            
                                                            Build.VERSION.SDK_INT: 25
                                                            Build.MANUFACTURER: smartisan
                                                            LeakCanary version: 2.4
                                                            App process name: fan.fragmentdemo
                                                            Analysis duration: 14196 ms
                                                            Heap dump file path: /storage/emulated/0/Download/leakcanary-fan.fragmentdemo/2020-06-27_15-19-38_530.hprof
                                                            Heap dump timestamp: 1593242395719
                                                            ====================================

以上是分析hprof文件的日志;

LeakCanary通过Shark解析hprof文件并定位在堆中无法回收被保留的对象;

done

以上是LeakCanary在堆堆文件找到被保留对象通知;

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

done

以上通知提示在计算被保留对象的引用路径;

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

done

以上表示4个引用路径分为两种不同的泄漏签名;

====================================
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查看更详细的泄漏问题,关闭Activity可以看到LeakCanary加载图标:

以上表示增加了一个为了每个被安装的app增加了一个加载图标;

每一行显示一组有详情签名的内存泄漏问题;LeakCanary标记了一行New表示第一次出现内存泄漏问题;

toast

以上表示4内存泄漏问题分在两行,每行有不同的泄漏签名;

点击打开带有泄漏引用路径。您可以通过下拉菜单在保留对象及其泄漏引用路径之间切换。

以上表示相同泄漏签名的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在你的app中分两类,Applications Leaks和Library Leaks;一个Library Leak是被第三方库引起的问题(超出你控制范围的);这个leak泄漏影响的应用程序,因为修改它可能不在你控制范围因此LeakCanary把它分开说明;

这两类被分开在Logcat控制台打印:

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

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

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

LeakCanary标记一行在leaks列表中做为Library Leak:

Library Leak

以上表示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

5.其他

我做了什么引起内存泄漏问题?

没问题!您按照预期的方式使用了一个API,但实现中有一个导致此泄漏的bug。

如果阻止内存泄漏问题?

可能!一些库泄漏可以使用反射修复,另一些可以通过使用使泄漏消失的代码路径修复。这种类型的修复往往是黑客,所以小心!您最好的选择可能是找到bug报告或文件,并坚持bug得到修复。

既然我对这次泄漏无能为力,有没有办法让LeakCanary置之不理呢?

在堆文件对其进行分析之前,LeakCanary无法知道泄漏是否是库泄漏。如果在发现库泄漏时,LeakCanary没有显示结果通知,那么您将开始怀疑在toast之后,LeakCanary分析发生了什么。

 

参考 :

https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值