Android 性能优化之一步步分析内存泄漏

本文主要是模拟一个简单的内存泄漏问题,来确定,分析,定位及解决问题。

在演示之前,我们有必要了解一点关于GC方面的知识。

Q:内存泄漏是怎么造成的?

  • 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

Q:什么是可回收对象

Java堆中存放着几所所有的对象实例,垃圾收集器在对堆进行回收前,首先需要确定哪些对象还”活着”,哪些已经”死亡”,也就是不会被任何途径使用的对象。

  • 引用计数法

引用计数法实现简单,效率较高,在大部分情况下是一个不错的算法。其原理是:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1,当引用失效时,计数器减1,当计数器值为0时表示该对象不再被使用。需要注意的是:引用计数法很难解决对象之间相互循环引用的问题,主流Java虚拟机没有选用引用计数法来管理内存。

  • 可达性分析算法。

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
引自:http://www.jb51.net/article/84235.htm
GC Root

Q : 哪几种对象可以作为GC Root?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象;

对上面问题有了了解之后,开始分析并解决,我们模拟的内存泄漏问题。


模拟情景:
编写2个Activity(A,B),让B内存泄漏。A(start) ->B(finish)->A(start),操作多次。

1,确定项目是否存在问题

我们通过Android Monitor 中的Memory来查看内存变化。
Free : 未分配的。Allocated:已分配的内存

  • 1,我们记录初次进入A时候的memory情况,可以看到初始值是13.17MB左右

    未泄露

  • 2,我们通过不断的‘A-B-A-B-A’之后,再次查看memory情况,值是14.16MB

    这里写图片描述

我们发现就2个简单的页面不断的跳转,最后返回A后,memory增大了1M,这是相当不合理的。

2,查看问题的位置

  • 操作步骤

    点击左侧的System Information一>Memory Usage。或者使用命令adb shell dumpsys meminfo [package_name] or [pid] (adb shell dumpsys meminfo com.xx.xx)。下图

    这里写图片描述

  • 查看结果

    我们发现总共2个Activity的应用。Activities数量有14个,Views数量有200多。

    这里写图片描述

  • 生成内存快照

    操作:Memory–Dump Java Heap ,下图

    这里写图片描述

  • 使用studio,查看内存快照

    通过上步操作,稍微等待,studio会自动打开内存快照,如下图。

    操作:把箭头的所指位置,切换到Pakcage Tree View下,找到我们自己写的类,右侧是查看有多少实例。下方是引用树。

    通过下图可以看出,我们返回A(MainActivity)之后,B页面本应该没有实例,但是却还有很多的实例存在。

    这里写图片描述

这里,我们确定了问题的位置。下面,我们将用MAT来确定更具体的位置。

3,使用MAT,找到具体的问题所在

  • 生成标准的hprof。

    **操作:**captures–内存快照—右键—Export to standard .hprof

    这里写图片描述

  • 使用histogram 分析

    这里写图片描述

  • 使用compare 来对比两个hprof文件。

    这里写图片描述

  • 过滤自己项目的包名,如下图

    会发现B(HandlerLeakActivity)对象比刚开始时候多了14个。

    这里写图片描述

  • 找到谁引用了这个对象

    操作:在泄漏的hprof-histogram上选择Group By Package(下图箭头位置),找到自己项目中,上方分析出来的类,右键List objects ->with outgoing references,找到谁应用了这个对象。

    这里写图片描述

    操作完成,如下图

    这里写图片描述

  • 确定最终谁导致的内存泄漏

    操作:全部选中 -> 右键 -> Merge Shortest Paths to GC Roots ->exclude all..

    合并最短路径的GC Roots 并排除软,弱,虚引用。

    这里写图片描述

    获取到最终的结果,如下图。
    Thread,Message,基本可以判断为Handler造成的。

    这里写图片描述

4,解决问题。

对于Handler造成的内存泄漏,基本有两个方式解决。
1,activity ondestry()时,removeCallback
2,使用弱引用+静态内部类的方式。

猜你喜欢

转载自blog.csdn.net/ecliujianbo/article/details/76438992