性能优化之内存泄漏分析
内存泄漏分析主要可以使用 Android Studio 的 Memory Monitor 工具以及 MAT来分析。
如下是一段用来测试的代码:
public class LeakActivity extends Activity { InnerClass mInnerClass; //static InnerClass mInnerClass; List<Button> mBtns = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); if(mInnerClass == null) { mInnerClass = new InnerClass(); } for(int i=0; i<1000; i++) { Button btn = new Button(this); mBtns.add(btn); } } class InnerClass { void doSomeThing() { System.out.println("doSomeThing"); } } }
一、Memory Monitor
打开Android Studio 的 Android Monitor,选择 Memory Monitor 可以实时查看到内存的动态分配情况。
正常没有内存泄漏的情况
我们从首页打开 LeakActivity 两次,然后再按返回键返回到首页。然后再进行手动 GC 两次,结果如下:
从图中可以看到经过两次GC后,内存回到最初进首页时的2M了。说明没问题,LeakActivity实例被回收了。
内存泄漏情况
给成员变量 mInnerClass 加上 static 关键字,然后重新运行程序并做同样的操作,结果如下:
可以看到进行两次手动 GC 后,内存还是没完全下去,有大概 8M 没回收掉。这时就出现了内存泄漏,有一个 LeakActivity 实例被回收不了。
以上图片只能大概看出内存的分配大小情况,对于想知道堆内存中存储了什么。还得用Heap Viewer。
Heap Viewer的使用
出现了内存泄漏的情况,我们来看下 Java Heap 情况。点击如下红色框内的 Dump Java Heap 按钮,就会生成 .hprof 文件。
生成 .hprof 文件后,Android Studio会自动打开如下 Heap Viewer 分析界面。
点击右上角区域中红色框起来的 Perform Analysis 按钮会进入HPROF Analyzer的hprof的分析界面:
可以看到右边框起来的Analyzer Tasks 区域中给出分析结果了,LeakActivity 发生泄漏了。
从下面的区域中可以看出 LeakActivity 被 this0 引用了,而 this0 是表示内部类的意思,可以看到这个内部类就是 InnerClass 。
二、MAT
MAT 比 Menmery Monitor 更强大。
MAT工具全称为Memory Analyzer Tool,下载地址:http://eclipse.org/mat/downloads.php。MAT是一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件。
1、获取hprof文件
从Android Studio 获取
当我们点击Memory Monitor 的 Dump Java Heap 按钮后,AS自动为我们生成了hprof 文件,点击左侧的 Captures 图标会列出 hprof 文件列表:
这时需要按图示转换成 MAT 可以识别的标准 hprof 文件,然后就可以使用MAT打开分析了。
使用DDMS获取
从AS 的Tools菜单中打开 Android Device Monitor。
然后就可以Android Device Monitor的界面了。
和上面做同样的操作,从首页打开 LeakActivity 两次,然后再按返回键返回到首页。然后点击下图画圈的按钮,进行手动 GC 两次。
然后再点击旁边的按钮获取 hprof 文件。
这个是Android的hprof文件,需要转换成 Java 的hprof文件,MAT才能打开。
转换工具需要使用 sdk里面的hprof-conv.exe,路径:sdk 的 platform-tools 目录下。
转换命令示例:
新生成的 output.hprof 就可以使用 MAT 打开分析了。
2、使用MAT分析
使用 MAT 打开 hprof 文件后,这时MAT就会生成报告,这个报告分为两个标签页,一个是Overview,一个是Leak Suspects(内存泄漏怀疑)。如下图:
Leak Suspects中会给出了MAT认为可能出现内存泄漏问题的地方,上图共给出了3个内存泄漏猜想,通过点击每个内存泄漏猜想的Details可以看到更深入的分析清理情况。如果内存泄漏不是特别的明显,通过Leak Suspects是很难发现内存泄漏的位置。
打开Overview标签页,首先看到的是一个饼状图,它主要用来显示内存的消耗,饼状图的彩色区域代表被分配的内存,灰色区域的则是空闲内存,点击每个彩色区域可以看到这块区域的详细信息,如下图所示:
再往下看,Actions一栏的下面列出了MAT提供的四种Action,其中分析内存泄漏最常用的就是 Histogram 和 Dominator Tree。我们点击Actions中给出的链接进行查看分析。
2.2.1 Dominator Tree
Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。
图中可以看出Dorminator Tree有三列数据。
Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。如果是数组类型的对象,它的大小是数组元素的类型和数组长度决定。如果是非数组类型的对象,它的大小由其成员变量的数量和类型决定。
Retained Heap:一个对象的Retained Set所包含对象所占内存的总大小。换句话说,Retained Heap就是当前对象被GC后,从Heap上总共能释放掉的内存。
Retained Set指的是这个对象本身和他持有引用的对象以及这些引用对象的Retained Set所占内存大小的总和。
通过MAT提供的Dominator Tree,可以很清晰的得到一个对象的直接支配对象,如果直接支配对象中出现了不该有的对象,就说明发生了内存泄漏。
在Dominator Tree的顶部Regex可以输入过滤条件(支持正则表达式),如果是查找Activity内存泄漏,可以在Regex中输入Activity的名称,比如我们这个例子可以输 LeakActivity,结果如下图所示。
可以看到还是有一个 LeakActivity 没有被回收掉,基本可以断定发生了内存泄漏,具体内存泄漏的原因,可以查看GC引用链。在LeakActivity 一项单击鼠标右键,选择 Paths to GC Root,如下图所示。
选择exclude all phantom/weak/soft etc. references,是因为这个选项排除了虚引用、弱引用和软引用,这些引用一般是可以被回收的。这时MAT就会给出LeakActivity 的GC引用链。
引用LeakActivity 的是InnerClass ,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是LeakActivity ,这将会导致LeakActivity 无法被GC。
2.2.2 Histogram
Histogram与Dominator Tree不同的是,Dominator Tree是在对象实例的角度上进行分析,注重引用关系分析,而Histogram则在类的角度上进行分析,注重量的分析。
Histogram中的内容如下图所示。
在Histogram的顶部Regex同样可以输入过滤条件,这里同样输入LeakActivity ,效果如下图所示。
LeakActivity 和InnerClass 实例各为1个,基本上可以断定发生了内存泄漏。具体内存泄漏的原因,同样可以查看GC引用链。在LeakActivity 一项单击鼠标右键,选择 Merge Shortest Paths to GC Root,如下图所示。
得到的 LeakActivity 的GC引用链如下:
得出的结果和2.2.1节是相同的,引用LeakActivity 的是InnerClass ,这导致了LeakActivity 无法被GC。
三、如何发现内存泄漏
上面分别介绍了使用Android studio和MAT分析内存的方法。Android studio自带的内存分析工具直观方便,但其功能却不如MAT强大,特别是没有有效的搜索、排序等功能。遇到一些棘手的问题,可能还是要借助MAT来分析内存。
上面的例子是我们人为制造了一个内存泄漏,然后有意用工具检测他。但实际开发中,我们如何发现内存泄漏呢?我想可以首先使用studio自带或DDMS中的heap分析工具,观察在反复执行某个操作时(例如打开某个页面、点击某个按钮、加载某个资源等等)时,内存在执行GC后能始终维持在稳定的值附近。如果内存呈线性增长的趋势,那一定是发生了内存泄漏。此时,就要dump出内存镜像,然后使用工具分析了。
在分析内存时,第一是可以使用工具自带的泄漏检查器帮助定位。另外,可以在执行操作(怀疑造成内存泄漏的操作)前后,分别dump出一份内存镜像,然后使用MAT的Compare Basket对比两个文件的内存情况,这样可以帮助定位到是哪个对象发生了泄漏。然后再找到这个对象的GC Roots,这样就可以进一步定位到具体的代码了。
发布评论
热门评论区: