https://blog.csdn.net/qq_20280683/article/details/77964208

Android内存泄漏的检测流程、捕捉以及分析

简述:

一个APP的性能,重度关乎着用户体验,而关于性能检测的一个重要方面,就是内存泄漏,通常内存泄漏的隐藏性质比较强,不同于异常导致的程序Crash,在异常导致的Crash中,我们能够及时的发现程序问题的存在,并通过log日志定位到问题所在的具体位置,然后及时进行解决,而内存泄漏则不同,在APP中存在内存泄漏的情况下,用户在低频率短时间的使用中,并不能察觉到有什么异样,反之,随着使用频率的提高和使用时长的增加,内存泄漏就会一直慢慢积累,消耗内存,从而会导致手机卡顿,直至APP崩溃,所以防止APP内存泄漏的出现,是至关重要的。

理论阐述内存泄漏导致的原因:

在android开发中,jvm具有自动回收的机制,会不定时不定期的去清理无用的被占用的内存,而在理论上不需要再被使用的内存,在实际中却还持有对这一块内存的引用,导致GC时,不会被回收释放掉,这部分内存就会随着程序的运行不断堆积,从而导致应用分配的内存不够使用导致卡顿、ANR异常等情况。

导语

关于内存泄漏的检测,我们分为了以下几个阶段:
1. 开发编码过程中,在开发过程中就不断对代码进行内存泄漏的检测
2. 项目或者模块开发完成后,对应用进行整体的内存泄漏检测
3. 在项目上线后,远程端检测项目是否存在内存泄漏的情况

一:开发编码过程中,检测内存泄漏

编码过程中可能导致的内存泄漏案例分析:

关于在编码中可能导致的泄漏案例以及编码注意事项,本文不做过多赘述,在给到的链接中,作者对泄漏案例描述的都比较详细,也比较全面,想了解的小伙伴可以【点击这里—>内存泄漏案例】

1.检测工具:LeakCanary

首先最容日上手并且效果还不错,那就要属LeakCanary,效果也直观,具体的使用配置也很简单。

在项目的build.gradle中加入以下引用:
// 内存存泄漏检测
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
1
2
3
4
在application中初始化LeakCanary,到此处配置完成
/** Explain : 初始化内存泄漏检测
* @author LiXaing
private void initLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
1
2
3
4
5
6
7
8
发生内存泄漏提示

当前就从网络上获取到了一张关于,在发生内存泄漏的时候,会在通知栏出现一个提示图标,当点击进去之后,就是现在展示的这张图片,会直观的展示内存泄漏的位置。

注意:==通过LeakCanary的使用,它可以为我们快速找到内存泄漏的位置,但并不能够提供我们内存泄漏的原因,有的时候,内存泄漏的位置是由于其他原因导致的,本人曾经就碰到过一次,由于Fragment未能被回收,从而导致了EventBus未能解绑(在onDestory中有解绑EventBus),导致的EventBus也存在内存泄漏,而导致的原因并非是没有解绑;所以内存泄漏的位置,并不一定就是导致泄漏的根本原因,所以后面及有可能还需要其他的工具进行辅助。==

2.检测工具:StrictMode

StrictMode在Android 2.3(API 9)的时候就已经引入了,虽然到当前这个工具年代比较久远了,但属实还是非常好用的, 在开发阶段使用这个工具,能够很好的帮助发现开发中的一系列不规范的编码,例如主线程访问网络,主线程读写磁盘,等等耗时操作,另外的一大特性就是可以帮助开发时,发现程序存在内存泄漏的情况。

StrictMode的功能主要分为两大块:

一块是关于Thread,线程规范的监测,另一块是关于VM,内存的监测。在StrictMode下分别是ThreadPolicy和VMPloicy。

ThreadPolicy:用于监测线程部分,监测 主线程中是否访问网络、主线程中是否读写磁盘等。

<!--代码示例:在application中进行配置即可-->
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()//监测所有内容
.penaltyLog()//违规对log日志
.penaltyDeath()//违规Crash
.build());
1
2
3
4
5
6
相关API:

API 描述
detectAll 监测所以违规内容
permitAll 禁用所以监测内容
permit**…如:permitNetwork 关闭检测网络访问违规
detectNetwork 监测主线程中是否存在有访问网络
detectDiskReads()、detectDiskWrites() 监测是否在主线程中读写磁盘
penaltyDeath 一旦触发任何违规操作就直接Crash掉程序
penaltyDeathOnNetwork 一旦触发网络访问违规操作就Crash掉程序
penaltyDialog 一旦触发违规操作,就弹出违规信息对话框
当然,以上列出的是常用的API,也还有其他的一些API没有列出来,在开发中以上的API基本够用了,要是还有其他需求的小伙伴,就自行去查找一下吧。

VMPolicy:用于监测内存,可以监测Activity的内存泄漏,Fragment的内存泄漏(虽然内部没有指定Fragment可用的API但内部关于类对象的检测机制在此处就有着异曲同工之妙的作用),SQL内存泄漏,是否正常关闭读写流以及可以指定某个类的最大对象数目。

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()//监测所以内容
.penaltyLog()//违规对log日志
.penaltyDeath()//违规Crash
.build());
1
2
3
4
5
API 描述
detectAll 监测所以违规内容
detectActivityLeaks 监测Activity内存泄漏的情况
detectLeakedClosableObjects() 和 detectLeakedSqlLiteObjects() 当使用的资源没有被正确关闭时会触发
detectLeakedRegistrationObjects 监测BroadcastReceiver 或者 ServiceConnection 注册类对象是否被正确释放
setClassInstanceLimit 设定某个类在内存中实例的上限
penaltyDialog 一旦触发违规操作,就弹出违规信息对话框
当然,以上列出的是常用的API,也还有其他的一些API没有列出来,在开发中以上的API基本够用了,要是还有其他需求的小伙伴,就自行去查找一下吧。

二:项目或者模块开发完成后,检测内存泄漏

在项目或者模块开发完成后,所需要做的就是各种测试,在交给测试人员之前,为了展现我们自身编码的高逼格和高质量,通常我们程序猿自己先会进行一部分测试,其中很重要的一部分测试就是本章内容讲的,内存泄漏的测试。

首先,我们可以使用android Studio中AndroidMonitor自带的一个工具—>memory,这个工具也可以说是非常的好用,先简单的介绍一下,memory虽然不可以分析出哪部分存在泄漏等情况,但可以很直观的看到内存的占用情况,看到内存的动态变化。

1. 我们打开AndroidMonitor,点击打开Memory,打开的界面,可能是这样的什么都没有。

这时我们需要需要注意两个点:
2. 可能是没有选择相关的进程,点击下三角,选择我们需要查看相关的进行。

如果还是什么都没有,我们点击 “Tools->Android->Enable ADBIntergration(勾选)”

操作好以上步骤之后,我们就可以看到上面这张图片的状态,可以很直观的看到,应用在内存中占用的情况。

使用Memory,并不能够分析出具体的是哪一部分存在内存泄漏,主要是用来查看内存的占用的动态情况

关于左侧上方四个按钮功能和用法的描述:

功能 描述
Memory的开关
(Initial GC) 用于手动GC,通常在抓取HPROF文件前,手动GC回收掉那些无用对象
(Dump Java Heap) 用于生成HPROF文件,HPROF文件中的的数据是点击这个生成按钮这一瞬java VM执行时共享Heap中的数据,共享Heap中的数据主要包含了类的实例和数组对象,通常用于内存泄漏分析,在点击之后就能够看到Memory recorder在转动,在稍许延迟之后就会生成一个HPROF文件。
(Starg Allocation Tracking) 用于动态追踪内存情况,可以记录下一段时间区间内各个线程中各个方法在内存中的分配情况,使用方式:点击Starg Allocation Tracking按钮,开始分配追踪,可以看到Memroy recorder在转动,在合适的时间再点击一下就完成了对一时间区间内内存动态的记录,在稍许延迟之后就会生成一个.alloc文件
Dump Java Heap生成的.HPROF文件

在我们点击 Dump Java Heap之后,就会生成一个.alloc文件,这个文件是分析内存泄漏非常重要的一部分。

当打开HPROF文件的时候是这样的,什么都没有,为了方便查找,我们点击”Class List View”选择–>”Package Tree View”当前展示就是变为包的视图树。

那么到了这一部分,我们应该查看哪儿是存在泄漏的呢?分为两种情况:

1. 其实如果到了这一步,基本上可以断定是存在内存泄漏了的,通过之前的LeakCanary、StrictMode已经可以判断出内存泄漏了,而这部分是为了通过LeakCanary、StrictMode给出的信息更确切的确定内存泄漏的部分在内存中运作的情况和更详细的数据。

2. ==首先需要明确的一点是LeakCanary、StrictMode并不能检查出所有的内存泄漏==,所以在我们对应用程序反复点击调试之后,需要我们人工审查这些类的实例,通常绝大多数类的实例例如Activity、Fragment等等每个类的实例是只有一个的,当然也是要看具体的程序实现来看待,在ViewPage中等情况就可以出现一个类有多个实例的情况,这也因实际情况来看待,如果超出了实际情况的实例对象数那么就很有可能是存在了内存泄漏。在我们人工审查这些类的实例通常都会先检查Activity、Fragment、自定义View等等的实例情况,因为伴随这些一起泄漏的都是高概率。

给出的上图,是Fragmnet反复打开关闭产生实例的泄漏,实际情况中只应该存在一个,而此处有14个,有人会问,那你怎么就知道就要查找这一个Fragmnet实例呢?在此,需要说明的一点是,不管是通过上面所说的第一种情况还是第二种,都可以排查到这类泄漏的;要问:那要我没有排查到呢?那只有说:眼瞎,就先去医院治病~

在给出的上图中,存在多个Fragment内存泄漏,我们就关注图中标注好的“1”部分,我们可以通过Total Count(总实例数)和Heap Count(堆内存中实例数)可以看到有14个,点击这个类,然后在旁边的Instance框中我们可以看到这14个具体的实例,也就是图中标注的“2”部分,我们点击第一个;然后在下方的Reference Tree中我们可以看到当前这个实例对象持有的具体的对象,那么在这一部分我们怎么排查呢?我们主要需要关注的是 Dominating Size(当前指向的这个一条,在内存中占有的大小)值最大的前面几条,为什么呢?因为泄漏导致内存无法被释放值越大,存在泄漏的可能性越大。 所以看上图中的“3”部分,我们针对这一条不断的向下展开引用,在“4”部分,我们可以很清晰的看到是由EventBus导致的内存泄漏,而在我给出的这个示例里,也就是由于Fragment在销毁时未EventBus导致的。

另外值得一说的标注的“5”,在Analyzer中有一个功能就是 Detect Leaked Activities,点击绿色三角按钮运行后,可以帮我们分析出当前可能存在泄漏的Activity对象。

到目前为止,我们通过HPROF文件在Studio中的使用可以完成内存泄漏的分析,不过还有更为强大的一款工具,更方便我们使用就是下要说的MAT

MAT(Memory Analyzer Tool)工具的使用

【还没有这个工具的小伙伴可以戳这里–>Eclipse Memory Analyzer Open Source Project】

首先我们需要打开在MAT中打开.hprof文件,在打开MAT工具后,点击右上角的 “File”-> “Open Heap Dump”,然后选择需要打开的.hprof文件,在打开的过程中可能会遇到这样的问题:

关于这个问题,有两种方式解决,比较建议的是第一种,第一种更方便,更容易操作:

一:

在Android Studio工具的右侧,点击上图红框标注的“1” CapTures;

选择红框标注的“2”Heap SanPshot选择需要导出的.hprof;

选择好需要导出的.hprof文件后,单机右键选择“Export to standard”(导出标准的.hprof文件);
4.然后选择导出保存到的位置,就可以了。

二:
Windows操作系统下,打开CMD黑窗口(win键+R键,输入cmd)

按照上图的操作写入输入输入路径,然后回车,就可以完成转换了。

接下来就是关于在MAT工具中的操作,MAT的功能很多,但就不一一介绍了,就简单说明关于在检测内存泄漏用到的部分。

选择上图红框的“Histogram”,然后我们可以看到下图界面

在上图中,我们看到的是一系列的数组对象和类的实例等数据,我们可以通过上图红框中的部分,输入需要查找的路径或者类的类名等进行过滤

在这一部分中,所需要观察的点,就是Objects列,是存在的实例对象数,我们选择好了需要排查的实例对象点击右键,选择“merge shortest Path to GC Roots” -> “exclude all phantom/weak/soft etc. references”,而为什么要进行这一步过滤呢,因为导致内存泄漏的只会是硬引用,所以我们可以把虚引用,弱引用,软引用全部进行过滤掉,可以减少误判。

在进行过滤之后,我们可以看到唯一持有的硬引用只有EventBus,那么这时,我们回到项目中去具体观察EventBus部分的代码就好了;而这,只是在实际情况中的一种,另外在过滤掉那三种引用之后,还存在有多种引用的情况,这个时候,需要观察的点就是“Objcets”(对象数量)和Showll Heap(在堆内存中占据的内存大小)数量越多和占据内存越高的,存在泄露的可能性越大,因为反复创建却不能够被回收,数量和占据的内存越高。

Android内存泄漏的检测流程、捕捉以及分析的更多相关文章

  1. Android 内存泄漏检测工具 LeakCanary(Kotlin版)的实现原理

    LeakCanary 是一个简单方便的内存泄漏检测框架,做 android 的同学基本都收到过 LeakCanary 检测出来的内存泄漏.目前 LeakCanary 最新版本为 2.7 版本,并且采用 ...

  2. Android内存泄漏检测利器:LeakCanary

    Android内存泄漏检测利器:LeakCanary MAR 28TH, 2016 是什么? 一言以蔽之:LeakCanary是一个傻瓜化并且可视化的内存泄露分析工具 为什么需要LeakCanary? ...

  3. android 内存泄漏检测工具 LeakCanary 泄漏金丝雀

    韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 内存泄漏检测工具 android 内存泄漏检测工具 ======== 内存泄漏 就是  无用的对 ...

  4. Android - 内存泄漏 + 垃圾回收(GC)概念

    Android内存泄露——全解析和处理办法 内存泄露 说到内存泄露,就不得不提到内存溢出,这两个比较容易混淆的概念,我们来分析一下. 内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未 ...

  5. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  6. [Android]Android内存泄漏你所要知道的一切(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7235616.html Android内存泄漏你所要知道的一切 ...

  7. Android内存泄漏原因

    这段时间调试APP的时候,发现程序在加载了过多的bitmap后会崩溃.查看了日志,原来是发生了内存溢出(OOM).第一次遇到这样的问题,那就慢慢排查吧. 内存优化可以参考胡凯大神的博客Android内 ...

  8. Android 内存泄漏总结(转)

    Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却 ...

  9. [转载]浅谈C/C++内存泄漏及其检测工具

    http://dev.yesky.com/147/2356147_3.shtml 对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题.已经有许多技术被研究出来以应对这个问题,比如Sm ...

随机推荐

  1. MySQL的SQL预处理(Prepared)

    Prepared SQL Statement:SQL的执行.预编译处理语法.注意点 一.SQL 语句的执行处理1.即时 SQL 一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下: 1. ...

  2. 【AI】基本概念-准确率、精准率、召回率的理解

    样本全集:TP+FP+FN+TN TP:样本为正,预测结果为正 FP:样本为负,预测结果为正 TN:样本为负,预测结果为负 FN:样本为正,预测结果为负 准确率(accuracy):(TP+TN)/ ...

  3. 垃圾wps弹出,现在连关闭按钮都不给了

    垃圾wps弹出,现在连关闭按钮都不给了,有点起色就变得相当垃圾.

  4. 【MyBatis学习06】_parameter:解决There is no getter for property named in class java.lang.String

    我们知道在mybatis的映射中传参数,只能传入一个.通过#{参数名} 即可获取传入的值. Mapper接口文件: public int delete(int id) throws Exception ...

  5. ES6 扩展运算符 三点(...)

    含义 扩展运算符( spread )是三个点(...).它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列. console.log(...[, , ]) // 1 2 3 conso ...

  6. 深度解剖session运行原理

    已经大半年没有更新博客了,一方面有比博客更重要的事情要做,另外一方面也没有时间来整理知识,所以希望在接下来的日子里面能够多多的写博客来与大家交流 什么是session session的官方定义是:Se ...

  7. mysql中的多表查询

    基本模式:t1 CROSS JOIN t2, t1 INNER JOIN T2 ON ,以及LEFTJOIN 和RIGHT JOIN. 这些都需要在实践中使用,多练习才行. 写一句sql语句:SELE ...

  8. java学习之路--String类方法的应用

    消除字符串两端的空格 1.判断字符串第一个位置是否为空格,如果是继续向下判断,直到不是空格位置,末尾也是这样,往前判断,直到不是空格为止. 2.当开始和末尾都不是空格时,获取字符串. public s ...

  9. django 初始命令

    1.安装django pip3 install django 2.创建一个Django对象 django-admin.py startproject 项目名称 django-admin.py star ...

  10. Java编程基础篇第一章

    计算机语言 人与计算机交流的方式. 计算机语言有很多种如:C语言,c++,Java等 人机交互 软件的出现实现了人与计算机之间的更好的交流(交互) 交互方式 图形化界面:便于交互,容易操作,简单直观, ...