泄漏原因:单例生命周期 = 应用程序的生命周期,若其持有Context是某个Activity,会造成内存泄漏。
解决方案:尽量使用ApplicationContext;或者使用弱引用(WeakReference)来进行改进。
1.2 非静态内部类 / 匿名类泄漏原因:非静态内部类和匿名类会自动持有外部类的强引用,且其生命周期可能大于外部类的生命周期,造成内存泄漏。
解决方案:将其改成静态内部类;或者使用弱引用(WeakReference)来进行改进。
1.3 集合类泄漏原因:集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露。
解决方案:在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。
1.4 其他的情况网络、文件等流忘记关闭; 手动注册广播时,退出时忘记 unregisterReceiver(); Service 执行完后忘记 stopSelf(); EventBus 等观察者模式的框架忘记手动解除注册; static 关键字修饰的成员变量。
二 使用DDMS和MAT工具进行内存优化 2.1 观察heap首先在Android Studio中打开Android Device Monitor(在Android Studio 3.0以上版本需要在命令行输入monitor呼出)
如图为Android Device Monitor界面,选择进程后,可以点击工具栏上的
(update heap)来更新统计信息,点击右侧的 Cause GC 按钮或工具栏上的
即可查看当前的堆情况。
主要关注两项数据:
1、Heap Size 堆的大小,当资源增加,当前堆的空余空间不够时,系统会增加堆的大小,若超过上限 (例如64M,视平台和具体机型而定)则会被杀掉。
2、 Allocated 堆中已分配的大小,这是应用程序实际占用的内存大小,资源回收后,此项数据会变小。
查看操作前后的堆数据,看是否有内存泄漏 。对单一操作(比如添加页,删除页)进行反复操作,如果堆的大小一直增加,则有内存泄漏的隐患。
2.2 利用MAT分析内存(Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation)2.2.1 获取hprof文件
DDMS 可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息,配合它的查找,对比功能,就可以定位内存泄漏的原因。
在应用进行足够多的操作后(或者使用monkey命令:adb shell monkey -p com.gala.video 100),点击工具栏上的
按钮,将内存信息保存成hprof文件。
2.2.2 转换hprof并打开
该文件还要经过转换才能被 MAT识别,Android SDK提供了这个工具 hprof-conv (位于 sdk/tools下),命令行输入:hprof-conv a.hprof b.hprof 即完成转换。
如下图所示,为MAT打开hprof文件后的overview界面:
2.2.3 Histogram 查询
(1)查找定位
Histogram可以列出每一个类的所有对象。可以分组,也可以查找。如下图所示在表的第一行可以输入正则表达式来匹配结果。
状态栏Objects标示该对象出现的次数,Shallow Heap标示对象本身所占的内存空间,Retained Heap表示对象本身及其直接引用或间接引用一起所占的内存空间。
一般来说若Shallow Heap和Retained Heap的差距很大就需要仔细分析该对象是不是产生了泄漏。
图中第一行NormalModeActivityProxy在运行过程中产生了3个对象,而Shallow Heap和Retained Heap的差距较大。基本可以确定其存在内存泄漏的问题。
(2)快速分析
快速找出某个实例没被释放的原因,可以右健Merge Shortest path to GC root -> exclude all phantom/weak/soft etc. references:找到从GC Root到一个对象或一组对象的共同路径,不包含虚、弱引用、软引用,剩下的就是强引用。
从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存溢出了。可以得到如下:
可以看到LoginCallbackRecorder这个单例类没有成功释放掉Listener,从而一直持有了NormalModeActivityProxy的强引用,导致后者无法被GC造成内存泄漏。