LeakCanary: 内存溢出检测工具

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 & < 5 threshold)

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


image.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容