什么是内存泄漏
对于不同的语言平台来说,进行标记回收内存的算法是不一样的,像 Android(Java)则采用 GC-Root 的标记回收算法。下面这张图就展示了 Android 内存的回收管理策略(图来自Google 2011的IO大会)
图中的每个圆节点代表对象的内存资源,箭头代表可达路径。当圆节点与 GC Roots 存在可达路径时,表示当前资源正被引用,虚拟机是无法对其进行回收的(如图中的黄色节点)。反过来,如果圆节点与 GC Roots 不存在可达路径,则意味着这块对象的内存资源不再被程序引用,系统虚拟机可以在 GC 过程中将其回收掉。
有了上面的内存回收的栗子,那么接下来就可以说说什么是内存泄漏了。从定义上讲,Android(Java)平台的内存泄漏是指 没有用的对象资源任与GC-Root保持可达路径,导致系统无法进行回收。 举一个最简单的栗子,我们在 Activity 的 onCreate 函数中注册一个广播接收者,但是在 onDestory 函数中并没有执行反注册,当 Activity 被 finish 掉时,Activity 对象已经走完了自身的生命周期,应该被资源回收释放掉,但由于没有反注册, 此时 Activity 和 GC-Root 间任然有可达路径存在,导致 Activity 虽然被销毁,但是所占用的内存资源却无法被回收掉。类似的栗子其实有很多,不一一例举了。对于 Android(Java)内存回收管理想要再深入了解的童鞋,可以看看下面资源:
可以作为GC Roots的对象包括下面几种:
虚拟机栈中引用的对象, 一般是当前在使用中局部变量
方法区中类静态属性引用的对象, 就是静态变量对应的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
MAT分析内存泄漏的时候,也是查看对象到GC Roots的引用链,来定位泄漏代码的位置。
所以未使用的对象直接或间接地被GC Roots引用时会让GC无法回收,从而产生内存泄漏。
Android 可能引起泄露的例子请参考
http://www.cnblogs.com/daqiang5566/p/6150235.html
静态代码分析工具 —— Lint
Lint 是 Android Studio 自带的工具,使用姿势很简单 Analyze -> Inspect Code 然后选择想要扫面的区域即可
对可能引起泄漏的编码,Lint 都会进行温馨提示。
这里只是抛砖引玉的介绍 Lint ,实际上玩法还有很多,大家可以自行拓展学习。除了 Lint 外,还有像 FindBugs 、 Checkstyle 等静态代码分析工具也是很不错的。
严苛模式 —— StrictMode
(1)是关于常用的监控方面的,
Disk Writes 磁盘写
Network access 网络访问
Custom Slow Code 自定义的运行速度慢的代码分析
(2)另外一类是关于VM虚拟机等方面的策略
内存泄露的SQLite对象
内存泄露的未释放的对象
StrictMode是Android系统提供的API,在开发环境下引入可以更早的暴露发现问题。官方文档链接在下面(需要*)。
https://developer.android.com/reference/android/os/StrictMode.html
以官网的示例代码为栗子,一般 StrictMode 只在测试环境下启用,到了生产环境就会进行关闭,通常我们都会借助 BuildConfig.DEBUG 来实现。
启用 StrictMode 后,在过滤日志的地方加上 StrictMode 的过滤 Tag ,如果手机连接着电脑进行开发,定期观察一下 StrictMode 这个 Tag 下的日志,一般你看到一大堆红色告警的 Log.就需要好好排查一下是否跟内存泄漏有关了。
详细的StrictMode 请参考:http://www.cnblogs.com/daqiang5566/p/6163756.html
Android Memory Monitor
AndroidStudio 提供的工具,用于监控应用的内存使用状态,在开发中也是非常实用的工具,可以用来打印出内存的状态信息。
打印获得的内存信息如下,可以通过右上角的绿色三角形按钮去分析泄漏的 Activity 和 一些重复的字符串,目前只支持这两个,希望 Google 后面能够加入更多可选分析规则
同样,这里也只是抛砖引玉的简单介绍,关于它的使用在官方文档已经说得很详细了,需要的童鞋自行查看下方链接(需*):
Memory Analyzer (MAT)
老牌子分析工具,可以从 http://www.eclipse.org/mat/ 下载获得,网上关于 MAT 使用的文章好多,大家可以自行查找。上面的 Android Memory Monitor 生成的对储存信息文件可以配置 MAT 一起来分析使用,由于 Android Memory Monitor 生成的 hprof 文件不是标准格式,所以需要做一下转换,然后导入 MAT
然后通过 OQL 先定位出泄漏的对象
通过排除除了强引用之外的其他引用链,最后分析到 GC Root 的位置
MAT 使用起来相对繁琐,但不失为定位根源问题的利器。
adb shell 命令
使用 adb shell dumpsys meminfo [PackageName],可以打印出指定包名的应用内存信息
使用该命令可以很直观的观察到 Activity 的泄漏问题,是我平常分析比较常用的一种方式。除了使用命令外,AndroidStudio 也提供了下面的功能,和使用命令是一样效果的。
如果对 adb shell 命令感兴趣,更多的信息可以看下面提供的资源:
以上就是我在做内存泄漏分析的时候会用到的工具,通常都是结合起来用,毕竟每个工具都有优缺点,通过使用多个工具互补分析问题可以极大的提高我们的效率和最终取得的效果。
内存泄露工具:leakcanary 也可以检车内存泄露问题,可自行百度
泄漏的解决策略
(1) 注意Activity的泄漏
- 内部类引用导致Activity泄漏
- Activity Context被间接引用
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露
(2) 注意静态变量和单例模式
静态变量是作为GC Roots,在Android其生命周期基本和进程一样长,所以要非常静态变量引用其他生命周期的对象。虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
(3) 注意容器中对象泄漏
有时候,我们为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的。例如,针对2.3的系统,如果把drawable添加到缓存容器,因为drawable与View的强应用,很容易导致activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,需要对2.3系统上的缓存drawable做特殊封装,处理引用解绑的问题,避免泄漏的情况。
(4) 注意监听器的注销
(5) …
(6) 及时关闭Cursor
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)
Google IO 2011 Memory management for Android Apps
泄漏的源头
了解完内存泄漏的理论知识后,再来归类一下内存泄漏的源头。这里我将其归位以下三类:
自身编码引起
由项目开发人员自身的编码造成。
第三方代码引起
这里的第三方代码包含两类:第三方非开源的SDK和开源的第三方框架。
系统原因
由 Android 系统自身造成的泄漏,如像 WebView 、 InputMethodManager 等引起的问题,还有某些第三方 ROM 存在的问题。
泄漏的定位
内存泄漏不像闪退的BUG,排查起来相对要比较困难些,比较极端的情况是当你的应用 OOM 了才发现存在内存泄漏问题,到了这种情况才去排查处理问题的话,对用户的影响就太大了。为此,我们能够在编码中尽早发现到问题就不要拖到上线之后才去填坑,下面介绍一些我比较常用排查内存泄漏的工具。
参考地址:http://magic.360.cn/index.html
1. APP安全检查
2. 代码规范检查
3. 内存泄露检查
4. 日志输出检查
5. 空指针检查
6. 多线程检查