内存管理

Catalogue   

概述

有时候,线上会出现一些”莫名其妙”的bug,比如用户用的好好的,突然就爆出一个空指针异常,这个异常的提示信息如下:

1
2
java.lang.RuntimeException: Unable to start activity
ComponentInfo{xxx.xxx.xxx/xxx.xxx.xxxActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'xxx' on a null object reference

看上去好像就是一般的空指针,但这个异常发生在启动Activity的时候。ActivityThread的performLaunchActivity方法确实有catch

1
2
3
4
5
6
7
8
9
...
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
...

那怎么会装样子呢?

做了以下分析:

猜测这个时候App都被回收了,在Application的onCreate方法中添加日志;在Activity的生命周期回调方法中添加日志。

等用户再次触发时,获取行为日志,查看。

果然,这个时候Application被重新create了,且没有走splash,直接打开了原来的某个页面,而这个页面时需要获取xxx数据的,但这个数据都还没初始化呢!

问题定位了,所以只需要在此种情况恢复都时候,重新初始化相关数据即可。

问题解决后,得想想为什么会出现这种情况呢?这个时候就该了解下Android是如何来管理内存的。

先看看官方文档:https://developer.android.com/topic/performance/memory-overview

内存不足管理

Android 有两种处理内存不足情况的主要机制:内核交换守护进程和低内存终止守护进程。

内核交换守护进程

kswapd是用来做虚拟缓存的进程,当设备上的可用内存不足时,该守护进程将变为活动状态。Linux 内核设有可用内存上下限阈值。当可用内存降至下限阈值以下时,kswapd 开始回收内存。
当可用内存达到上限阈值时,kswapd 停止回收内存。

低内存终止守护进程

很多时候,kswapd 不能为系统释放足够的内存。在这种情况下,系统会使用 onTrimMemory() 通知应用内存不足,应该减少其分配量。如果这还不够,内核会开始终止进程以释放内存。
它会使用低内存终止守护进程 (LMK) 来执行此操作。

LMK 使用一个名为 oom_adj_score 的“内存不足”分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。
下表列出了从高到低的 LMK 评分类别。评分最高的类别,即第一行中的项目将最先被终止:

  • 后台应用:之前运行过且当前不处于活动状态的应用。LMK 将首先从具有最高 oom_adj_score 的应用开始终止后台应用。
  • 上一个应用:最近用过的后台应用。上一个应用比后台应用具有更高的优先级(得分更低),因为相比某个后台应用,用户更有可能切换到上一个应用。
  • 主屏幕应用:这是启动器应用。终止该应用会使壁纸消失。
  • 服务:服务由应用启动,可能包括同步或上传到云端。
  • 可觉察的应用:用户可通过某种方式察觉到的非前台应用,例如运行一个显示小界面的搜索进程或听音乐。
  • 前台应用:当前正在使用的应用。终止前台应用看起来就像是应用崩溃了,可能会向用户提示设备出了问题。
  • 持久性(服务):这些是设备的核心服务,例如电话和 WLAN。
  • 系统:系统进程。这些进程被终止后,手机可能看起来即将重新启动。
  • 原生:系统使用的极低级别的进程(例如,kswapd)。

经验总结

为了能让App尽量少出错,采取了以下的一些办法:

  1. 添加Activity回调,判断当前是否是”异常”启动,如果是,进行相应处理;
  2. 监听lowmemory,释放调可以释放的内存,比如Glide的资源占用;
  3. 平常写代码时注意一些写法,比如:
    • 不要在for循环、onDraw中频繁创建对象
    • 尽量使用Android优化过的数据结构,比如SparseArray

参考