LeakCanary: 内存溢出检测工具

时间:2021-7-3 作者:qvyue

LeakCanary Git地址

使用方法

目前最新版本是2.7,使用起来比较简单,只需要在gradle里面加入一句话就可以了.而且debugImplementation只在debug模式下有效,所以不用担心用户在正式环境下也会出现LeakCanary收集。

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

在项目中加入LeakCanary之后就可以开始检测项目的内存泄露了,把项目运行起来之后, 开始随便点自己的项目,下面以一个Demo项目为例,来聊一下LeakCanary记录内存泄露的过程以及我如何解决内存泄露的。
项目运行起来之后,在控制台可以看到LeakCanary的打印信息:

2021-05-26 10:47:44.894 3704-3751/com.frj.memerytest D/LeakCanary: LeakCanary is running and ready to detect memory leaks.
2021-05-26 10:47:46.443 3704-3742/com.frj.memerytest D/LeakCanary: Setting up flushing for Thread[LeakCanary-Heap-Dump,5,main]
2021-05-26 10:44:00.461 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of android.arch.lifecycle.ReportFragment (android.arch.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key f1a14db2-ae06-401c-9e3b-32e5484201bb
2021-05-26 10:44:00.462 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of com.mingjian.memerytest.MemoryOomActivity (com.mingjian.memerytest.MemoryOomActivity received Activity#onDestroy() callback) with key db22bbc6-cfbd-4259-86fd-67f4d84dda3e

这说明LeakCanary正在不断的检测项目中是否有剩余对象。那么LeakCanary是如何工作的呢?LeakCanary的基础是一个叫做ObjectWatcher Android的library。它hook了Android的生命周期,当activity和fragment 被销毁并且应该被垃圾回收时候自动检测。这些被销毁的对象被传递给ObjectWatcher, ObjectWatcher持有这些被销毁对象的弱引用(weak references)。如果弱引用在等待5秒钟并运行垃圾收集器后仍未被清除,那么被观察的对象就被认为是保留的(retained,在生命周期结束后仍然保留),并存在潜在的泄漏。LeakCanary会在Logcat中输出这些日志。

2021-05-26 10:44:04.279 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of android.arch.lifecycle.ReportFragment (android.arch.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key ec552863-1e83-4435-a701-081e39bf87ae
2021-05-26 10:44:04.280 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of com.mingjian.memerytest.MemoryShake (com.mingjian.memerytest.MemoryShake received Activity#onDestroy() callback) with key 1f1259f2-a8ba-417c-8493-e0fa1c27fd3f
2021-05-26 10:44:09.394 17731-17793/com.frj.memerytest D/LeakCanary: Found 2 objects retained, not dumping heap yet (app is visible & 

我一直来回进入内测泄漏和内存抖动的页面,一会就有通知提示了,点击这个通知,可以在手机上查看信息

LeakCanary: 内存溢出检测工具
image.png

LeakCanary: 内存溢出检测工具
image.png

此时也可以在logcat上查看信息

====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
227457 bytes retained by leaking objects
Displaying only 1 leak trace out of 3 with the same signature
Signature: ad8152279f1def7a0b3dedc96584fda8974225
┬───
│ GC Root: System class
│
├─ android.os.Looper class
│    Leaking: NO (Thread↓ is not leaking and a class is never leaking)
│    ↓ static Looper.sMainLooper
├─ android.os.Looper instance
│    Leaking: NO (Thread↓ is not leaking)
│    ↓ Looper.mThread
├─ java.lang.Thread instance
│    Leaking: NO (the main thread always runs)
│    Thread name: 'main'
│    ↓ Thread.threadLocals
│             ~~~~~~~~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap instance
│    Leaking: UNKNOWN
│    Retaining 1.3 kB in 44 objects
│    ↓ ThreadLocal$ThreadLocalMap.table
 │                                 ~~~~~
 ├─ java.lang.ThreadLocal$ThreadLocalMap$Entry[] array
 │    Leaking: UNKNOWN
 │    Retaining 1.3 kB in 43 objects
 │    ↓ ThreadLocal$ThreadLocalMap$Entry[].[7]
 │                                         ~~~
 ├─ java.lang.ThreadLocal$ThreadLocalMap$Entry instance
 │    Leaking: UNKNOWN
 │    Retaining 28 B in 1 objects
 │    ↓ ThreadLocal$ThreadLocalMap$Entry.value
 │                                       ~~~~~
 ├─ android.animation.AnimationHandler instance
 │    Leaking: UNKNOWN
 │    Retaining 457.9 kB in 6954 objects
 │    ↓ AnimationHandler.mAnimationCallbacks
 │                       ~~~~~~~~~~~~~~~~~~~
 ├─ java.util.ArrayList instance
 │    Leaking: UNKNOWN
 │    Retaining 457.8 kB in 6950 objects
 │    ↓ ArrayList.elementData
 │                ~~~~~~~~~~~
 ├─ java.lang.Object[] array
 │    Leaking: UNKNOWN
 │    Retaining 457.8 kB in 6949 objects
 │    ↓ Object[].[3]
 │               ~~~
 ├─ android.animation.ValueAnimator instance
 │    Leaking: UNKNOWN
 │    Retaining 76.3 kB in 1158 objects
 │    ↓ ValueAnimator.mUpdateListeners
 │                    ~~~~~~~~~~~~~~~~
 ├─ java.util.ArrayList instance
 │    Leaking: UNKNOWN
 │    Retaining 75.9 kB in 1145 objects
 │    ↓ ArrayList.elementData
 │                ~~~~~~~~~~~
 ├─ java.lang.Object[] array
 │    Leaking: UNKNOWN
 │    Retaining 75.9 kB in 1144 objects
 │    ↓ Object[].[0]
 │               ~~~
 ├─ view.IOSStyleLoadingView1$1 instance
│    Leaking: UNKNOWN
│    Retaining 75.8 kB in 1143 objects
│    Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
│    ↓ IOSStyleLoadingView1$1.this$0
│                             ~~~~~~
├─ view.IOSStyleLoadingView1 instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    Retaining 75.8 kB in 1142 objects
│    View is part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mWindowAttachCount = 1
│    context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│    mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│    ↓ IOSStyleLoadingView1.context
╰→ com.mingjian.memerytest.MemoryShake instance
     Leaking: YES (ObjectWatcher was watching this because com.mingjian.memerytest.MemoryShake received
     Activity#onDestroy() callback and Activity#mDestroyed is true)
     Retaining 7.5 kB in 119 objects
     key = 6723e90d-1567-4e34-99d7-714f96d0aa42
     watchDurationMillis = 22266
      retainedDurationMillis = 17261
      mApplication instance of android.app.Application
      mBase instance of android.app.ContextImpl
 ====================================
 0 LIBRARY LEAKS

路径中的每一个节点都对应着一个java对象。熟悉java内存回收机制的同学都应该知道”可达性分析算法“,LeakCanary就是用可达性分析算法,从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。

我们从上往下看:

GC Root: System class

在泄漏路径的顶部是GC Root。GC Root是一些总是可达的特殊对象。
接着是:

─ android.os.Looper class
│Leaking: NO (Thread↓ is not leaking and a class is never leaking)
│    ↓ static Looper.sMainLooper

这里先看一下Leaking的状态(YES、NO、UNKNOWN),NO表示没泄露。那我们还得接着向下看。一直到view.IOSStyleloadingView1 instance 才出现Yes

├─ view.IOSStyleLoadingView1 instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    Retaining 75.8 kB in 1142 objects
│    View is part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mWindowAttachCount = 1
│    context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│    mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│    ↓ IOSStyleLoadingView1.context

这说明这里出现了内存泄漏,这里直接指出来是我们自写的View出现了内存泄漏
一般推断内存泄露是从最后一个没有泄漏的节点(Leaking: NO )到第一个泄漏的节点(Leaking: YES)之间的引用。

 ├─ view.IOSStyleLoadingView1$1 instance
 │    Leaking: UNKNOWN
 │    Retaining 75.8 kB in 1143 objects
 │    Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
 │    ↓ IOSStyleLoadingView1$1.this$0
 │                             ~~~~~~
 ├─ view.IOSStyleLoadingView1 instance
 │    Leaking: YES (View.mContext references a destroyed activity)
 │    Retaining 75.8 kB in 1142 objects
 │    View is part of a window view hierarchy
 │    View.mAttachInfo is null (view detached)
 │    View.mWindowAttachCount = 1
 │    context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
 │    mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
 │    ↓ IOSStyleLoadingView1.context
 ╰→ com.mingjian.memerytest.MemoryShake 

可以看到liaking状态是YES之前是AnimatorUpdateListener,那就说明是此listener没有做释放,非常精准的定位问题了

对于每个被保留的对象,LeakCanary会找出阻止该保留对象被回收的引用链:泄漏路径。泄露路径就是从GC ROOTS到保留对象的最短的强引用路径的别名。确定泄漏路径以后,LeakCanary使用它对Android框架的了解来找出在泄漏路径上是谁泄漏了。

leak状态说明

NO: 没有内存泄漏
UNKNOWN: 表示这里可能出现了内存泄露,这些引用你需要花时间来调查一下,看看是哪里出了问题。
YES: 表示此处有内存泄漏

参考文章 https://www.jianshu.com/p/a5e69a2e093f

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。