App 专项测试之内存泄漏
一、知识点
1.1 内存泄漏分类(以发生的方式区分)
-
常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行的时候会导致一块内存泄漏
-
偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性也许就变成了常发性。
-
一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放内存,所以内存泄漏只会发生一次。
-
隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才会释放内存。严格来说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
1.2 内存溢出
为了整个Android系统的内存控制需要,Android系统为每一个应用程序都设置了一个硬性的Dalvik Heap Size最大限制阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果应用占用内存空间已经接近阈值,此时再尝试分配内存,就很容易引起OutOfMemoryError错误。
查询当前应用阈值的两种方法:
- ActicityManager.getMemoryClass()
- adb shell getprop dalvik.vm.heapgrowthlimit
应用启动后分配的初始内存: adb shell getprop dalvik.vm.heapstartsize
单个java虚拟机最大的内存限制: adb shell getprop dalvik.vm.heapsize
二、查看内存指令
在Android检查内存泄漏,主要搜索Activity、Fragment、View有没有泄漏。
2.1 adb 指令
adb shell dumpsys meminfo 查看所有应用的进程PID
adb shell top | grep 应用对应PID 实时显示指定进程的资源占用情况
- PID -- 进程id
- PR -- 优先级
- CPU% -- 瞬时CPU占用率
- S -- 进程状态,S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数
- #THR -- 程序当前所用的线程数
- VSS -- Virtual Set Size 虚拟好用内存(包含共享库占用的内存)
- RSS -- Resident Set Size 实际使用屋里内存(包含共享库占用的内存)
- PCY --
- UID -- 运行当前进程的用户uid
- Name -- 进程名
2.2 Android Studio的Memory Monitor
2.3 LeakCanary
2.4 DDMS查看内存MAT进行分析
2.5 Android ActivityManager.MemoryInfo()
使用 Android 自身提供的ActivityManager.MemoryInfo()方法,可以通过该方法获取某应用的内存信息,如网易Emmagee、腾讯的GT等工具
2.6 使用android提供的procrank获取
注:该方式需要获取root权限
adb shell procrank | grep packagename
- VSS – Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
- RSS – Resident Set Size 实际使用物理内存(包含共享库占用的内存)
- PSS – Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
- USS – Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS,其中USS只能通过procrank获取,首先网上下载libpagemap.so, procmem, procrank,然后push到android手机中。有的root机自带这几个文件,不需要额外下载。
三、 如何避免内存泄漏
3.1 注意Activity的泄漏
内部类引用导致Activity泄漏,具体见 Android中由Handler和内部类引起的内存泄漏
Activity Context被间接引用,对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露
3.2 注意静态变量和单例模式
静态变量是作为GC Roots,在Android其生命周期基本和进程一样长,所以要非常静态变量引用其他生命周期的对象。虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
3.3 注意容器中对象泄漏
有时为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的。例如,针对2.3的系统,如果把drawable添加到缓存容器,因为drawable与View的强应用,很容易导致activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,需要对2.3系统上的缓存drawable做特殊封装,处理引用解绑的问题,避免泄漏的情况。
3.4 注意监听器的注销
3.5 及时关闭Cursor
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。