LeakCanary,30分钟从入门到精通
简述
在性能优化中,内存是一个不得不聊的话题;然而内存泄漏,显示已经成为内存优化的一个重量级的方向。当前流行的内存泄漏分析工具中,不得不提的就是LeakCanary框架;这是一个集成方便, 使用便捷,配置超级简单的框架,实现的功能却是极为强大的。
不骗你,真的,使用就是这么简单 ?!
1. 你需要添加到配置的只有这个
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
2. 你肯定需要初始化一下,当然, 推荐在Application中
public class MyApplicationextends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
3. 什么?你还在下一步?已经结束了!通过以上配置,你就可以轻松使用LeakCanary检测内存泄漏了
关于LeakCanary的详细使用教程,建议去看:LeakCanary中文使用说明
闲聊结束,我想你们来肯定不是看我这些废话的,那么现在进入正题!本篇我们所想的,就是LeakCanary为什么可以这么神奇,它是怎么检测内存泄漏的?下面我们解开谜题!
《LeakCanary原理》 核心类分析
01 LeakCanary 源码解析
02 LeakCanary SDK提供类
03 DisplayLeakActivity 内存泄漏的查看页面
04 HeapAnalyzerService 内存堆分析服务, 为了保证App进程不会因此受影响变慢&内存溢出,运行于独立的进程
05 HeapAnalyzer 分析由RefWatcher生成的堆转储信息, 验证内存泄漏是否真实存在
06 HeapDump 堆转储信息类,存储堆转储的相关信息
07 ServiceHeapDumpListener 一个监听,包含了开启分析的方法
08 RefWatcher 核心类, 翻译自官方: 检测不可达引用(可能地),当发现不可达引用时,它会触发 HeapDumper(堆信息转储)
09 ActivityRefWatcher Activity引用检测, 包含了Activity生命周期的监听执行与停止
通过以上列表,让大家对LeakCanary框架的主要类有个大体的了解,并基于以上列表,对这个框架的大体功能有一个模糊的猜测。
漫无目的的看源码,很容易迷失在茫茫的Code Sea中,无论是看源码,还是接手别人的项目,都是如此;因此,带着问题与目的性来看这些复杂的东西是很有必要的,也使得我们阅读效率大大提高;想要了解LeakCanary,我们最大的疑惑是什么,我列出来,看看是与你不约而同。
Question1: 在Application中初始化之后,它是如何检测所有的Activity页面的 ?
Question2: 内存泄漏的判定条件是什么 ? 检测内存泄漏的机制原理是什么?
Question3: 检测出内存泄漏后,它又是如何生成泄漏信息的? 内存泄漏的输出轨迹是怎么得到的?
回顾一下这个框架,其实我们想了解的机制不外乎三:
1. 内存泄漏的检测机制
2. 内存泄漏的判定机制
3. 内存泄漏的轨迹生成机制
我们会在源码分析最后,依次回答以上的三个问题,可能在阅读源码之前,我们先要对内存泄漏做一些基础概念与原理的理解。
什么是内存泄漏(MemoryLeak)?
大家对这个概念应该不陌生吧,当我们使用一个Bitmap,使用完成后,没有recycle回收;当我们使用Handler, 在Activity销毁时没有处理;当我们使用Cursor,最后没有close并置空;以上这些都会导致一定程度上的内存泄漏问题。那么,什么是内存泄漏?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
以上是百度百科的解释,总结下为:内存泄漏是不使用或用完的内存,因为某些原因无法回收,造成的一种内存浪费;内存泄漏的本质是内存浪费。以个人理解来解释,通俗一点就是
1. GC回收的对象必须是当前没有任何引用的对象
2.当对象在使用完成后(对我们而言已经是垃圾对象了), 我们没有释放该对象的引用,导致GC不能回收该对象而继续占用内存
3.垃圾对象依旧占用内存,这块内存空间便浪费了
内存泄漏与内存溢出的区别是什么?
从名称来看,一个泄漏,一个溢出,其实很好理解。
内存泄漏: 垃圾对象依旧占据内存,如水龙头的泄漏,水本来是属于水源的, 但是水龙头没关紧,那么泄漏到了水池;再来看内存,内存本来应 该被回收,但是依旧在内存堆中;总结一下就是内存存在于不该存在的地方(没用的地方)
内存溢出: 内存占用达到最大值,当需要分配内存时,已经没有内存可以分配了,就是溢出;依旧以水池为例, 水池的水如果满了,那么如果继 续需要从水龙头流水的话,水就会溢出。总结一下就是,内存的分配超出最大阀值,导致了一种异常
明白了两者的概念,那么两者有什么关系呢?
内存的溢出是内存分配达到了最大值,而内存泄漏是无用内存充斥了内存堆;因此内存泄漏是导致内存溢出的元凶之一,而且是很大的元凶;因为内存分配完后,哪怕占用再大,也会回收,而泄漏的内存则不然;当清理掉无用内存后,内存溢出的阀值也会相应降低。
JVM如何判定一个对象是垃圾对象?
该问题也即垃圾对象搜索算法,JVM采用图论的可达遍历算法来判定一个对象是否是垃圾对象, 如果对象A是可达的,则认为该对象是被引用的,GC不会回收;如果对象A或者块B(多个对象引用组成的对象块)是不可达的,那么该对象或者块则判定是不可达的垃圾对象,GC会回收。

以上科普的两个小知识:1) 内存泄漏 2) JVM搜索算法 是阅读LeakCanary源码的基本功,有助于源码的理解与记忆。好了,下面来看一下LeakCanary的源码,看看LeakCanary是怎么工作的吧!
既然LeakCanary的初始化是从install()开始的,那么从init开始看


回顾一下核心类模块可知,内存分析模块是在独立进程中执行的,这么设计是为了保证内存分析过程不会对App进程造成消极的影响,如使App进程变慢或导致out of Memory问题等。因此
第一步: 判断APP进程与内存分析进程是否属于同一进程;如果是, 则返回空的RefWatcher DISABLED;如果不是,往下走,看第二步


第二步: enableDisplayLeakActivity 开启显示内存泄漏信息的页面


第三步:初始化一个ServiceHeapDumpListener,这是一个开启分析的接口实现类,类中定义了analyze方法,用于开启一个DisplayLeakService服务,从名字就可以看出,这是一个显示内存泄漏的辅助服务

第四步:初始化两个Watcher, RefWatcher和ActivityRefWatcher. 这两个Watcher的作用分别为分析内存泄漏与监听Activity生命周期


通过以上代码分析,我们可以得出第一个问题的答案。LeakCanary通过ApplicationContext统一注册监听的方式,来监察所有的Activity生命周期,并在Activity的onDestroy时,执行RefWatcher的watch方法,该方法的作用就是检测本页面内是否存在内存泄漏问题。
下面我们继续来分析核心类RefWatcher中的源码,检测机制的核心逻辑便在RefWatcher中;相信阅读完这个类后,第二个问题的答案便呼之欲出了。
既然想弄明白RefWatcher做了什么,那么先来看一下官方的解释

从上面图可以看出官方的解释。 RefWatcher是一个引用检测类,它会监听可能会出现泄漏(不可达)的对象引用,如果发现该引用可能是泄漏,那么会将它的信息收集起来(HeapDumper).
从RefWatcher源码来看,核心方法主要有两个: watch() 和 ensureGone()。如果我们想单独监听某块代码,如fragment或View等,我们需要手动去调用watch()来检测;因为上面讲过,默认的watch()仅执行于Activity的Destroy时。watch()是我们直接调用的方法,ensureGone()则是具体如何处理了,下面我们来看一下

上图为watch()的源码, 我们先来看一下官方的注释
监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的
从上述源码可以看出,watch里面只是执行了一定的准备工作,如判空(checkNotNull), 为每个引用生成一个唯一的key, 初始化KeyedWeakReference;关键代码还是在watchExecutor中异步执行。引用检测是在异步执行的,因此这个过程不会阻塞线程。

以上是检测的核心代码实现,从源码可以看出,检测的流程:
1) 移除不可达引用,如果当前引用不存在了,则不继续执行
2) 手动触发GC操作,gcTrigger中封装了gc操作的代码
3) 再次移除不可达引用,如果引用不存在了,则不继续执行
4) 如果两次判定都没有被回收,则开始分析这个引用,最终生成HeapDump信息
总结一下原理:
1. 弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中;通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。
2. 为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。
上面的代码,总结下流程就是
判定是否回收(KeyedWeakReference是否存在该引用), Y -> 退出, N -> 向下执行
手动触发GC
判定是否回收, Y -> 退出, N-> 向下执行
两次未被回收,则分析引用情况:
1) humpHeap : 这个方法是生成一个文件,来保存内存分析信息
2) analyze: 执行分析
通过以上的代码分析,第二个问题的答案已经浮出水面了吧!
接下来分析内存泄漏轨迹的生成~
最终的调用,是在RefWatcher中的ensureGone()中的最后,如图

很明显,走的是heapdumpListener中的analyze方法,继续追踪heapdumpListener是在LeakCanary初始化的时候初始化并传入RefWatcher的,如图

打开进入ServiceHeapDumpListener,看里面实现,如图

调用了HeapAnalyzerService,在单独的进程中进行分析,如图

HeapAnalyzerService中通过HeapAnalyzer来进行具体的分析,查看HeapAnalyzer源码,如图

进行分析时,调用了openSnapshot方法,里面用到了SnapshotFactory

从上图可以看出,这个版本的LeakCanary采用了MAT对内存信息进行分析,并生成结果。其中在分析时,分为findLeakingReference与findLeakTrace来查找泄漏的引用与轨迹,根据GCRoot开始按树形结构依次建议当前引用的轨迹信息。
通过上述分析,最终得出的结果为:
1. Activity检测机制是什么?
答: 通过application.registerActivityLifecycleCallbacks来绑定Activity生命周期的监听,从而监控所有Activity; 在Activity执行onDestroy时,开始检测当前页面是否存在内存泄漏,并分析结果。因此,如果想要在不同的地方都需要检测是否存在内存泄漏,需要手动添加。
2. 内存泄漏检测机制是什么?
答: KeyedWeakReference与ReferenceQueue联合使用,在弱引用关联的对象被回收后,会将引用添加到ReferenceQueue;清空后,可以根据是否继续含有该引用来判定是否被回收;判定回收, 手动GC, 再次判定回收,采用双重判定来确保当前引用是否被回收的状态正确性;如果两次都未回收,则确定为泄漏对象。
3. 内存泄漏轨迹的生成过程 ?
答: 该版本采用eclipse.Mat来分析泄漏详细,从GCRoot开始逐步生成引用轨迹。
通过整篇文章分析,你还在疑惑么?
链接:https://www.jianshu.com/p/1e7e9b576391
LeakCanary,30分钟从入门到精通的更多相关文章
- 30分钟Maven入门到精通
Maven是近年来最流行的项目构建与管理工具.不仅简化了我们开发过程中对jar包依赖的导入,还对项目的清理.初始化.编译.测试.打包.集成测试.验证.部署和站点生成等所有构建过程进行了抽象和统一,方便 ...
- 2016windows(10) wamp 最简单30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world
2016最简单windows(10) wamp 30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world thrift是什么 最简单解释 thrift是用来帮助各个编程语 ...
- AngularJS 30分钟快速入门【译】
引用自:http://www.revillweb.com/tutorials/angularjs-in-30-minutes-angularjs-tutorial/,翻译如下: 简介 我三年前开始使用 ...
- Python 30分钟快速入门指南
学习地址 中文版:Python 30分钟入门指南 英文版:Learn X in Y minutes 学习时间 2019/03/10 19:00 - 19:32,多用了2分钟.
- WebSocket协议:5分钟从入门到精通
一.内容概览 WebSocket的出现,使得浏览器具备了实时双向通信的能力.本文由浅入深,介绍了WebSocket如何建立连接.交换数据的细节,以及数据帧的格式.此外,还简要介绍了针对WebSocke ...
- 30 分钟快速入门 Docker 教程
原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. 一.欢迎来到 Docker 世界 1. ...
- vue.js-vue入门教程教你如何html中使用vue(30分钟快速入门)
前后端分离.微服务框架是当下比较流行的词汇,而vue就是前端框架的佼佼者.下面重点介绍一下vue的用法: vue起步:1.引包 2.启动new Vue({el:目的地,template:模板内容 ...
- Scala入门到精通
原文出自于: http://my.csdn.net/lovehuangjiaju 感谢! 也感谢,http://m.blog.csdn.net/article/details?id=52233484 ...
- Shell脚本编程30分钟入门
Shell脚本编程30分钟入门 转载地址: Shell脚本编程30分钟入门 什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd ~ mkdir shell_tut cd shell_t ...
随机推荐
- 《python源码剖析》,看看
这书高级了,有点超出理解能力. 但走出舒适区,不是大家都在说的么?:) 看完了些章节,还是很有收获的, 截图存照.
- Spring Boot 实战与原理分析视频课程
Spring Boot 实战与原理分析视频课程 链接:https://pan.baidu.com/share/init?surl=PeykcoeqZtd1d9lN9V_F-A 提取码: 关注公众号[G ...
- yum list失败
搭建了本地yum源,用yum list 测试报如下错误: [root@heguol ~]# yum list error: rpmdb: BDB0113 Thread/process ye67ww ...
- HBase0.99.2集群的搭建步骤(在hadoop2.6.4集群和zookeeper3.4.5集群上)
HBase介绍(NoSql,不是关系型数据库) HBase是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,利用HBASE技术可在廉价PC Server上搭建起大规模结构化存储集群. HBase ...
- Map知识点Utilities后续整理(关于Collections,Array,增强for循环)
一:介绍 1.介绍 里面都是静态方法. 可以直接调用. Collections是集合框架中的一个工具类.该类中的方法都是静态的 提供的方法中有可以对list集合进行排序,二分查找等方法. 通常常用的集 ...
- 内存缓存 ehcache
内存缓存需要对内存缓存每个参数的配置意义搞明白,才能很好地去使用,例如失效时间.存活时间. 是否存储在磁盘.是否永久有效,参数要了解清楚后进行使用,不要在不清楚时盲目使用,会导致意想不到 的问题发生. ...
- Bzoj5209[Tjoi2012]防御:姿势题
首先这题现在在BZOJ上是没数据的,你可以选择python2B获得AC,也可以去洛谷上交.选择第一个选项的现在可以不用看了...... 关于这题的题意,击破的一次攻击即使溢出也不双倍,否则你过不了样例 ...
- Codeforces.744B.Hongcow's Game(交互 按位统计)
题目链接 \(Description\) 一个\(n\times n\)的非负整数矩阵\(A\),保证\(A_{i,i}=0\).现在你要对每个\(i\)求\(\min_{j\neq i}A_{i,j ...
- [JSOI2015]最小表示
题目大意:尽可能多地去掉一个有向无环图上的边,使得图的连通性不变. 思路:拓扑排序,然后倒序求出每个结点到出度为$0$的点的距离$d$,再倒序遍历每一个点$x$,以$d$为关键字对其出边降序排序,尝试 ...
- Codeforces 986D Perfect Encoding FFT
题意: 给定一个数n,选出m个数使得 $\Pi_{i=1}^m a_i\ge n$,求$\sum_{i=1}^m a_i$的最小值 ,其中$m$的大小不限 $n$的长度$\le 10^6$ 简单的计算 ...