说说内存溢出?

2019-11-19  本文已影响0人  Joseph_L

哪些情况下会导致oom问题?

基本概念

首先明确一点,内存泄漏和内存溢出是不同的,但是过多的内存泄漏会导致内存溢出。

根据 java 的内存模型,会出现内存溢出的内存有堆内存、方法区内存、虚拟机栈内存、native方法区内存,一般说的 OOM 基本都是针对堆内存。

堆内存溢出的根本原因有两种:

  1. app 进程内存达到上限
  2. 手机可用内存不足

需要注意的是,手机可用内存不足一般是硬件原因,所以我们重点应该解决的是app的内存达到上限的问题。

具体原因

App 内存达到上限的原因也有两点:

  1. 应用申请内存的速度超出 gc 释放内存的速度

    • 往内存中加载超大文件
    • 循环创建大量对象
  2. 内存出现泄漏,gc无法回收泄漏的内存,导致可用内存越来越少,直至超过了应用的可用内存

    一般申请内存的速度超出gc释放内存基本不会出现,内存泄漏才是出现问题的关键所在

    • 资源对象没关闭造成的内存泄漏(如: Cursor、File等)
    • 全局集合类强引用没清理造成的内存泄漏(特别是 static 修饰的集合)
    • 接收器、监听器注册没取消造成的内存泄漏,如广播,EventsBus
    • Activity 的 Context 造成的泄漏
    • 单例中的static成员间接或直接持有了activity的引用
    • 非静态内部类持有父类的引用,如非静态 handler 持有 activity 的引用

优化方向

根据之前的分析,可以发现主要的问题在于应用可用内存较少,不良的代码习惯以及操作不当导致的内存泄漏。所以,相对应的有四个优化方向。

  1. 为应用申请更大内存,把 manifest 上的 largdgeheap 设置为 true;

  2. 优化内存的使用,减少大内存的开销

    • 使用优化后的集合对象,比如 SpaseArray;

    • 使用微信的 mmkv 替代 sharedpreference;

    • 对于经常打 log 的地方使用 StringBuilder 来组拼,替代 String 拼接;

      直接使用String会创建销毁多个实例,对性能有影响,StringBuffer只会一直操作同一个StringBuffer对象,就优化了内存使用

    • 统一带有缓存的基础库,特别是图片库,如果用了两套不一样的图片加载库就会出现2个图片各自维护一套图片缓存,无形中就加大了内存开销;

    • 给 ImageView 设置合适尺寸的图片,列表页显示缩略图,查看大图显示原图;

    • 优化业务架构设计,做好懒加载,避免一次性加载过多资源,浪费内存;

  3. 避免内存泄漏

    • 编码规范
      • 资源对象用完一定要关闭,最好加finally
      • 静态集合对象用完要清理
      • 接收器、监听器使用时候注册和取消成对出现
      • Context 使用注意生命周期,长生命周期引用直接用 ApplicationContext
      • 使用静态内部类,避免匿名内部类和非静态内部类,弱引用外部 Context 对象,确保对象可以被正确回收
    • 建设内存监控体系
      • 线下监控
        1. 使用 ArtHook 检测图片尺寸是否超出 imageview 自身宽高的2倍
        2. 编码阶段 Memery Profile 看 app 的内存使用情况,是否存在内存抖动,内存泄漏,结合 Mat 分析内存泄漏
      • 线上监控
        1. 上报app使用期间待机内存、重点模块内存、OOM率
        2. 上报整体及重点模块的 GC 次数,GC时间
        3. 使用 LeakCannery 自动化内存泄漏分析
  4. 兜底策略

    在低内存状态回调,根据不同的内存等级主动做一些事情

    比如在最严重的等级清空所有的bitmap,关掉所有界面,直接强制把app跳转到主界面,相当于app重新启动了一次一样,这样就避免了系统Kill应用进程,与其让系统 kill 进程抛异常,还不如浪费一些用户体验,自己主动回收内存。

上一篇下一篇

猜你喜欢

热点阅读