哪些情况下会导致oom问题?
基本概念
首先明确一点,内存泄漏和内存溢出是不同的,但是过多的内存泄漏会导致内存溢出。
根据 java 的内存模型,会出现内存溢出的内存有堆内存、方法区内存、虚拟机栈内存、native方法区内存,一般说的 OOM 基本都是针对堆内存。
堆内存溢出的根本原因有两种:
- app 进程内存达到上限
- 手机可用内存不足
需要注意的是,手机可用内存不足一般是硬件原因,所以我们重点应该解决的是app的内存达到上限的问题。
具体原因
App 内存达到上限的原因也有两点:
-
应用申请内存的速度超出 gc 释放内存的速度
- 往内存中加载超大文件
- 循环创建大量对象
-
内存出现泄漏,gc无法回收泄漏的内存,导致可用内存越来越少,直至超过了应用的可用内存
一般申请内存的速度超出gc释放内存基本不会出现,内存泄漏才是出现问题的关键所在
- 资源对象没关闭造成的内存泄漏(如: Cursor、File等)
- 全局集合类强引用没清理造成的内存泄漏(特别是 static 修饰的集合)
- 接收器、监听器注册没取消造成的内存泄漏,如广播,EventsBus
- Activity 的 Context 造成的泄漏
- 单例中的static成员间接或直接持有了activity的引用
- 非静态内部类持有父类的引用,如非静态 handler 持有 activity 的引用
优化方向
根据之前的分析,可以发现主要的问题在于应用可用内存较少,不良的代码习惯以及操作不当导致的内存泄漏。所以,相对应的有四个优化方向。
-
为应用申请更大内存,把 manifest 上的 largdgeheap 设置为 true;
-
优化内存的使用,减少大内存的开销
-
使用优化后的集合对象,比如 SpaseArray;
-
使用微信的 mmkv 替代 sharedpreference;
-
对于经常打 log 的地方使用 StringBuilder 来组拼,替代 String 拼接;
直接使用String会创建销毁多个实例,对性能有影响,StringBuffer只会一直操作同一个StringBuffer对象,就优化了内存使用
-
统一带有缓存的基础库,特别是图片库,如果用了两套不一样的图片加载库就会出现2个图片各自维护一套图片缓存,无形中就加大了内存开销;
-
给 ImageView 设置合适尺寸的图片,列表页显示缩略图,查看大图显示原图;
-
优化业务架构设计,做好懒加载,避免一次性加载过多资源,浪费内存;
-
-
避免内存泄漏
- 编码规范
- 资源对象用完一定要关闭,最好加finally
- 静态集合对象用完要清理
- 接收器、监听器使用时候注册和取消成对出现
- Context 使用注意生命周期,长生命周期引用直接用 ApplicationContext
- 使用静态内部类,避免匿名内部类和非静态内部类,弱引用外部 Context 对象,确保对象可以被正确回收
- 建设内存监控体系
- 线下监控
- 使用 ArtHook 检测图片尺寸是否超出 imageview 自身宽高的2倍
- 编码阶段 Memery Profile 看 app 的内存使用情况,是否存在内存抖动,内存泄漏,结合 Mat 分析内存泄漏
- 线上监控
- 上报app使用期间待机内存、重点模块内存、OOM率
- 上报整体及重点模块的 GC 次数,GC时间
- 使用 LeakCannery 自动化内存泄漏分析
- 线下监控
- 编码规范
-
兜底策略
在低内存状态回调,根据不同的内存等级主动做一些事情
比如在最严重的等级清空所有的bitmap,关掉所有界面,直接强制把app跳转到主界面,相当于app重新启动了一次一样,这样就避免了系统Kill应用进程,与其让系统 kill 进程抛异常,还不如浪费一些用户体验,自己主动回收内存。
网友评论