安卓内存泄露修复
前言
内存泄露,这个耳熟能详的一个bug,每次我在项目中遇到这种问题都非常的头疼,不像普通的异常奔溃比如JE什么的,只要通过Log中的异常堆栈就可以轻松的定位出问题点。那么到底什么是内存泄漏呢?简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。导致这种情况原因是生命周期长的对象持有了生命周期短的对象导致的生命周期短的对象无法被回收。要想定位出内存泄露问题,我们可以通过目前主流的MAT,Android Profiler,LeakCanary等工具来定位问题。
一、MAT简介
MAT是专门用于分析内存的工具,在Eclipse中只要安装相应的插件即可。用于解析反应当前设备内存映像的hprof文件,可以很直观的开出当前抓到的内存情况。一般该文件包含如下内容:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
- 所有的类信息,包括classloader、类名称、父类、静态变量等
- GCRoot到所有的这些对象的引用路径
- 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
二、Android Profiler简介
Android Studio 3.0 采用全新的Android Profiler窗口取代 Android Monitor 工具。 这些全新的分析工具能够提供关于应用 CPU、内存和网络 Activity 的实时数据。 您可以执行基于样本的函数跟踪来记录代码执行时间、采集堆转储数据、查看内存分配,以及查看网络传输文件的详情。
三、如何抓取hprof文件
hprof文件有三种方式来获取:1、DDMS抓取。2、adb命令行抓取,3、Android Studio抓取。有时抓取到的hprof格式并不是标准的可以使用如下命令转换
hprof-conv <source path + source + filename> <out path + out + filename>
1、DDMS抓取:
image如上图所示可以快速抓取到SystmeUI当前内存情况的hprof文件。
2、adb命令行抓取(如果没有权限开启可以尝试adb shell setprop ro.debuggable 1方式来开启io权限):
1、adb shell am dumpheap <pid/processname> <out path + filename>(必须要指定out目录)
2、adb shell pull <path + xx.hprof> <out path>
通过以上两个命令即可抓取到hprof文件
3、Android Studio抓取(基于Android 3.0以上):
首先打开Android Profier窗口我们可以看到如下的内容:
第一部分用于展示CPU的使用状态,第二部分用于显示内存使用情况,第三部分展示网络状态。今天我们的重点是第二部分的内存(MEMORY)。点击MEMORY进入专门的内存展示界面,如图:
image
该界面可以实时的展示当前进程内存的使用情况,包含了JAVA,NATIVE等内存。并通过不同的颜色来区分,可以非常方便的查看分析问题。如果知道内存泄露的必现路径,可以在该界面清晰的看到内存的增长。
然后通过如下方法就可以dump出hprof文件:
image
通过Android Profier生成的hprof文件也不是标准的,AS提供了便捷的转换方式:AS提供了便捷的转换方式:Memory Monitor生成的hpof文件都会显示在AS左侧的Captures标签中,在Captures标签中选择要转换的hpof文件,并点击鼠标右键,在弹出的菜单中选择Export to standard.hprof选项,即可导出标准的hpof文件,如下图所示。
image
Tips : 如果有的同学找不到Android Profiler窗口可以通过如下方式打开:View > Tool windows > Android Profile
四、如何通过MAT分析谁泄露(本文主要讲常用的分析方法)
通过MAT打开hprof文件,选择Leak Suspects Report选项,就会生产分析报告。主要分为两个部分:1、Overview。2、Leak Suspects(泄漏猜想)。如下如所示:
该分析报告可以基本看到内存的使用情况,以及一些泄漏猜想(一般定位不出问题来)。主要是通过查看Histogram 或者 Dominator Tree来分析问题。
Histogram
首先我们先说说Histogram。Histogram主要通过类的角度分析,注重的是量的分析。通过点击工具栏上
image 按钮选择柱状图方式分析。内容如图:image
主要有四个方面的内容。1、Class Name。2、Objects(该类的个数)。3、ShallowHeap(该类本身所占用的大小)。4、Retained Heap。
Retained Heap:一个对象的Retained Set所包含对象所占内存的总大小。换句话说,Retained Heap就是当前对象被GC后,从Heap上总共能释放掉的内存。(Retained Set指的是这个对象本身和他持有引用的对象以及这些引用对象的Retained Set所占内存大小的总和)官方的图解如下所示:
image
从图中可以看出E的Retained Set为E和G。C的Retained Set为C、D、E、F、G、H。
MAT所定义的支配树就是从上图的引用树演化而来。在引用树当中,一条到Y的路径必然会经过X,这就是X支配Y。X直接支配Y则指的是在所有支配Y的对象中,X是Y最近的一个对象。支配树就是反映的这种直接支配关系,在支配树中,父节点直接支配子节点。下图就是官方提供的一个从引用树到支配树的转换示意图。
image
C直接支配D、E,因此C是D、E的父节点,这一点根据上面的阐述很容易得出结论。C直接支配H,这可能会有些疑问,能到达H的主要有两条路径,而这两条路径FD和GE都不是必须要经过的节点,只有C满足了这一点,因此C直接支配H,C就是H的父节点。通过支配树,我们就可以很容易的分析一个对象的Retained Set,比如E被回收,则会释放E、G的内存,而不会释放H的内存,因为F可能还引用着H,只有C被回收,H的内存才会被释放。
假如说我们要找出Activity的情况,我们可以在Regex中输入XXActivity过滤条件来搜索。Regex支持正则表达式
image
RecentActivity以及其他各种内部类对象(Activity后面的美元符号代表的是内部类)有3个,可以断定Activity出现了泄漏。具体查看为啥导致了内存泄漏,可以通过右击选择Merge Shortest Paths to GC Roots选择最小GC的路径,然后选择exclude all phantom/weak/soft etc.references去除一些软,弱引用等对象,找到真正的强引用对象。如下图:
image
选择之后,得出一份如下分析结果:
image
明显可以得出是由于$7的这个匿名内部类持有了Activity对象,导致出现了内存泄漏。刚好遇到的是匿名内部类,无法得知该内部类到底是谁。这时我们只能通过代码查找来查看哪个内部类出现了问题。
Dominator Tree
Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。注重的是引用关系。
同样我们通过在Regex过滤出XXActivity对象
image
可以发现有好几个RecentsActivity的对象,同样可以断定是RecentsActivity出现了泄露。这边有三种方式分析谁泄露:1、发现RecentsActivity7出现了泄露。2、通过Path to GC Roots的找出GC roots路径方式来如图:
image
3、通过Merge Shortest Paths to GC Roots的方式,该方式上面已经分析过了就不继续展开了。
以上通过两种方法查找出了内存泄露的内容。
五、如何通过Android Profiler分析内存泄露
Memory Profiler 是 Android Profiler 中的一个组件,可帮助您识别导致应用卡顿、冻结甚至崩溃的内存泄漏和流失。 它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
下面我们通过一个例子来总结内存泄露问题的分析过程:
- 首先我们操作内存泄露的必现路径,通过上面介绍的AS抓hprof的方式来抓取泄露hprof文件。
- 然后就会直接显示出当前的JAVA对象以及引用的情况。我们可以选择左上角Arrayge by class下拉弹窗来显示app heap的排列方式:
- 通过Class来排序:基于类名称对所有分配进行分组。
- 通过Package来排序:基于软件包名称对所有分配进行分组。
- 通过CallbackBack:将所有分配分组到其对应的调用堆栈。
image
- 然后选择左上角的向上图标导出hprof文件:
image
然后重新将hprof文件通过AS打开,选择右上角的Analyzer Tasks
image
在点击那个播放按钮可以自动分析出Activity与String泄露的内容。然后选择其中一个Activity就可以看出该Activity被谁给持有
image
发现同样也是RecentsActivity7对象持有导致的泄露。可以注意看持有Activity那个对象会是蓝色显示的,且前面有也有类似于MAT小黄点估计与MAT的小黄点是一个意思。
六、参考链接
- Android内存优化(五)详解内存分析工具MAT
- Android内存优化(三)避免可控的内存泄漏
- Android 内存泄漏总结
- 使用MAT工具分析性能(有些左下角有小黄点,带有这个小黄点的就表示可以被GC ROOT访问到,可以被GC Root访问到的对象是不可以被回收的,当然不代表带有小黄点的就是内存泄漏的,也有些是一直被系统所使用的。其实也不算内存泄漏因为在这三个后面都是显示的System Class,代表着它被系统所占用)
- Android性能优化:彻底解决内存泄漏
- HPROF Viewer and Analyzer
- 使用 Memory Profiler 查看 Java 堆和内存分配