Activity内存泄漏分析
前言
这是第一次书写博客,工作也有几年了,虽然写了很多的笔记,但是都属于闭门造车,有时候也不知道自己记录的经验有没有问题,或者是不是有理解不到位的地方。因此想做一下改变,将自己记录的东西发到博客上,集思广益。如果我的博客对解决你的问题有帮助,那也是一件不错的事情。如果觉得我分享内容有错误,也请指出来,方便本人改正免得误导大家。好了,接下来开始分享一下我最近遇到的一个Activity内存泄漏的问题,主要讲一下遇到内存泄漏,如何分析定位问题的。下面分析中的例子是我自己根据原来的问题,书写的一个样例代码。
问题背景
项目上有一个音乐应用,客户报该应用在使用一段时间后,内存占用较高,高达了600多M,正常应该在300M左右。
使用dumpsys meminfo获取应用内存信息
使用指令adb shell dumpsys meminfo + 应用包名 查看应用内存使用情况,例如:
1.Native Heap是native层的内存堆栈,Dalvik Heap是java层的内存堆栈,如果这二者加起来的内存占用超过了应用最大内存限制就会报OOM异常,剩下的.so mmap是 C 库代码占用的内 存,.jar mmap是Java 文件代码占用的内存 ,.apk mmap是apk代码占用的内存,.dex mmap是Dex 文件代码占用的内存。
2. Objects中的Activities表示当前内存中的activity对象的个数,启动一个activity就会生成一个activity对象,当退出activity的时候,activity对象就会被释放,所以反复的进出一个activity界面然后查看Activities的个数有没有保持不变,如果增加了,那么就说明这个activity对象没有被释放,也就是说可能存在内存泄漏,但是具体哪里泄漏了,需要继续分析。
3.当前这份dump出来的数据,是我写的Demo中,MainActivity和SecondActivity之间来回切换后的结果,可以看到Objects中的Activities个数达到了31个,我这里没有贴出来应用原始的dumpsys meminfo数据,否则也可以发现Total这项总内存也在一直增加,且多次GC后回落的也并不明显,说明当前应用可能存在内存泄漏,接下来则需要通过抓取应用的hprof文件来分析。
DDMS抓取hprof文件
通过Android SDK中自带的工具Android Device Monitor(DDMS)抓取应用的hprof文件,monitor工具的地址在Sdk/tools目录下。
-
首先在ubuntu终端输入monitor打开程序
-
点击右上角选择DDMS工具,找到你需要调试的应用包名,选中指定进程后(例如com.example.myapplication),点击界面左上角的"update
Heap"按钮,获取应用的内存信息,截图如下:上图是应用的Heap信息是应用初始的内存信息,主要关注data object这项,其中Count是总对象的个数,Totle Size是java对象占用的总内存。比如当前的这个Demo,反复的进入SecondActivity,其中Count的个数在不断增加,退出该SecondActivity后,执行”Cause GC”并等待一段时间后,发现Count个数以及Total Size并没有明显回落,那么这个应用可能就存在内存泄漏,需要具体分析一下。我这边使用的内存分析工具为MemoryAnalyzer-1.8.1.20180910-linux.gtk.x86_64.zip,这个工具在网上很容易下载到,这里就不提供连接了;
-
抓取hprof文件 ,多次点击“Cause GC”触发一下系统的GC内存回收,然后等待GC完毕,此时是程序最开始占用内存的情况,接下来反复的进入某个Activity,操作完毕后,多次点击”Cause GC"执行内存回收,然后点击DDMS界面左上角的"dump hprof file"按钮抓取Heap文件,一般文件默认名为:com.example.myapplication.hprof;这时候抓取的hprof文件是不能直接被MAT工具打开的,因此需要进行一下转换;
-
使用hprof-conv工具将Heap文件转换为MAT工具可以读取的格式,Ubuntu的小伙伴可以将位于SDK/platform-tools下的hprof-conv配置到环境变量中。首先打开 ~/.bashrc文件在末尾添加:export PATH=/home/xxx/Android/Sdk/platform-tools:$PATH 然后在终端使用如下指令转换hprof文件:hprof-conv source.hprof target.hprof
MAT分析hprof
- 使用MAT工具打开hprof文件,截图如下:
- 选择Histogram查看应用中每个Class的对象个数(先查看对象),打开后界面如下:
首先对图中这些条目代表什么意思进行一下说明,Objects代表该对象的个数,Shallow Heap表示当前对象占用的内存,不包括当前对象内包含的对象的大小,Retained Heap表示当前对象包括对象内的子对象一共占用的内存大小,所以Retained Heap会比Shallow Heap大;
由于一个应用的hprof文件中对象太多,如果想要快速找到SecondActivity,这里其实还有一种办法就是MAT工具的OQL功能,我们可以通过在OQL里面输入“select * from instanceof android.app.Activity”,点击左上角感叹号图标,执行Query语句,可以快速查找,具体截图如下:
- 我写的Demo,反复进入的Activity的名字为com.example.myapplication.SecondActivity;从第二点的第一张截图中可以看出该Activity的对象个数达到了30个,正常情况下退出该Activity后,该Activity的对象应该被回收才对,所以很明显我写的这个SecondActivity存在内存泄漏。
- 选中SecondActivity,右键选择merge shortest Paths to GC Roots->exclude all
phantom/weak/soft .ect references排除所有的弱引用,看是否有GC
Roots持有该对象的强引用。如果没有GC Roots则说明这个对象不存在内存泄漏,最终是会被GC回收的。执行操作后得到如下截图:
上图中com.android.internal.policy.DecorView$TileScreenWindowModeListener作为GC Roots(GC Roots对象左下角有一个小圆点),持有了SecondActivity的mContext。而我们知道Activity在退出后,里面的资源对象是会被系统回收的,这里有GC Roots持有了Activity的强引用,导致该Activity不能被GC回收,因此发生内存泄漏。具体TileScreenWindowModeListener里面为什么会在SecondActivity退出后仍然持有它的mContext,就需要具体分析代码了。
这里对点击Object对象后,右键的几个选项进行一下说明:List Objects中with outgoing references(表示的是当前对象,引用了哪些外部引用)和with incoming references(表示的是当前查看的对象,被哪些外部对象引用),一般当前对象泄漏了就是对象还被外部对象持有引用,无法被释放。
参考链接如下: https://www.jianshu.com/p/e39fa2900960
来源:oschina
链接:https://my.oschina.net/u/4304562/blog/4658793