最近工作接触了很多内存泄漏的问题,希望写这篇文章来记录一下一些查找和解决内存泄露问题的方法技巧,算是对这段时间的一些总结,希望能更深入理解内存泄露的相关原理,也方便以后的工作。
1、java内存分布
线程私有的区域不会发生GC,他们的生命周期与线程相同,只有堆区和方法区发生GC,其中堆区包含新生代和年老代,方法区包含永久代,GC的顺序是新生代 > 年老代 > 永久代。
堆区(Heap Area)主要包含实例对象(包含对象的成员变量)、数组。
栈区(Stack Area)主要包含基本类型数据和对象引用。
方法区(Method Area)包含类信息、构造函数、静态变量、常量等。
2、什么是内存泄露
为了能够正确释放内存,GC会监视每个对象的状态,如对象的内存申请、引用关系等。回收内存(GC)的根本原则是对象不再被引用(可达性分析算法)。如果某个对象到GC root没有任何一条引用链,则说明这个对象不再被引用。
GC root包含以下几种:
(1)虚拟机栈中引用的对象(局部变量的引用对象)
(2)本地方法栈中引用的对象(Native方法中引用的对象)
(3)方法区中静态变量引用的对象
(4)方法区中常量引用的对象
内存泄漏就是,程序错误地持有对象的引用,使得GC对该对象没有回收。
3、内存泄露的检测
本人采用的方法是自动化测试,使用自动化工具,写好测试脚本,反复某个路径测试,用下面的adb命令实时监控进程使用的内存
①Adb shell ps | grep email 获取email进程的pid
②while true;do dumpsys meminfo pid | grepTOTAL;sleep 1;done;
在自动化测试时,配合MAT或者leakcanary等检测工具,检测泄漏路径
4、Android常见内存泄漏
(1)非静态内部类和匿名内部类
非静态内部类和匿名内部类默认持有对外部类的引用,稍不注意就会导致内存泄漏。典型的就是Handler类、Thread类、Runnable接口、Listener监听器等内存泄漏。可使用静态内部类+弱引用的方式解决此类内存泄漏,或者handler.removeCallback
Handler类:looper中未被处理或正在处理的消息引用了内部handler类,内部handler类持有外部activity的引用
Thread类:内部类thread默认持有外部Activity的引用
Runnable接口:匿名内部类默认持有外部Activity的引用
Listener监听器:同上
(2)静态变量
被static关键字定义的静态变量,其生命周期与程序的生命周期相同,在程序结束运行之前,静态变量都不会被回收。如果静态变量引用耗费资源过多的实例(如ActivityContext),则会造成内存泄漏。可使用弱引用或者修改为引用ApplicationContext来修改此类内存泄漏。
(3)资源未关闭造成的内存泄漏
Cursor、File、Bitmap、BroadcastReceiver等需要在activity销毁时关闭或者注销,以免造成内存泄漏
参考:
https://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/