OOM
oom(out of memory) Android系统对 dalvik 的 vm heapsize 作了硬性限制,当 java 进程申请的 java 空间超过阈值时,就会抛出OOM异常。可以通过adb shell getprop 或者 getprop dalvik.vm.heapgrowthlimit 查看此阈值。
getprop dalvik.vm.heapgrowthlimit
256m
PRODUCT_PROPERTY_OVERRIDES += \
dalvik.vm.heapstartsize=8m \
dalvik.vm.heapgrowthlimit=64m \
dalvik.vm.heapsize=256m \
dalvik.vm.heaptargetutilization=0.75 \
dalvik.vm.heapminfree=512k \
dalvik.vm.heapmaxfree=8m
我们用下面的代码获取heapsize:
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
int maxHeapSize = manager.getLargeMemoryClass();
程序发生 OMM 并不表示 RAM 不足,而是因为程序申请的 java heap 对象超过了 dalvik vm heapgrowthlimit。也就是说,在 RAM 充足的情况下,也可能发生 OOM(这句话比较关键)
这样设计的目的是为了让 Android 系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。迫使每个应用程序使用较小的内存,移动设备非常有限的RAM就能使比较多的app常驻其中。
进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。
以下两个概率区分Dalvik Heap 和Native Heap
native进程:采用C/C++实现,不包含dalvik实例的linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的
java进程:实例化了 dalvik 虚拟机实例的 linux 进程,进程的入口 main 函数为 java 函数
130|console:/ # dumpsys meminfo com.funshion.ottedu
Applications Memory Usage (in Kilobytes):
Uptime: 9506057 Realtime: 9506057
** MEMINFO in pid 1187 [com.funshion.ottedu] **
Pss Private Private Swap Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 33913 33848 0 0 35692 48244 34778 2811
Dalvik Heap 4135 3960 0 0 7220 12945 6473 6472
Dalvik Other 2468 1940 0 0 3288
Stack 1012 1012 0 0 1016
Ashmem 2 0 0 0 8
Other dev 104 0 104 0 324
.so mmap 5411 204 300 0 41620
.jar mmap 2859 0 40 0 28928
.apk mmap 6124 180 2520 0 11280
.ttf mmap 92 0 0 0 312
.dex mmap 14715 20 14608 0 14960
.oat mmap 75 0 0 0 2148
.art mmap 6362 6052 0 0 14632
Other mmap 2098 40 924 0 5184
GL mtrack 37872 37872 0 0 37872
Unknown 3686 3684 0 0 3920
TOTAL 120928 88812 18496 0 120928 61189 41251 9283
C/C++ 申请的内存空间在 native heap 中,而 java 申请的内存空间则在 dalvik heap中
Java 程序发生 OMM 并不是表示 RAM 不足。 如果 RAM 真的不足,会发生什么呢?这时 Android 的 memory killer 会起作用(OOMKiller),当 RAM 所剩不多时,memory killer 会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序得到更多的内存。
参考链接:Android 中的 Dalvik Heap 和 Native Heap
OOMKiller
OOMKiller 是Linux Kernel 的内存监控机制。会在内存紧张的时候,会依次kill内存占用较高的进程,并在/var/log/message中进行记录。里面会记录一些如pid,process name,cpu mask,trace等信息,通过监控可以发现类似问题。
Mar 10 23:40:13 ...... kernel: xxx invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Mar 10 23:40:13 ...... kernel: [<ffffffff811171b1>] oom_kill_process+0x241/0x390
Mar 10 23:40:13 ...... kernel: [<ffffffff81116c4d>] ? oom_unkillable_task+0xcd/0x120
Mar 10 23:40:13 ...... kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj
在我们底层通过Malloc 申请内存,根据Linux有一种内存机制,会返回一个非空的内存,但是Linux并不保证这些内存马上可用(虚拟内存->物理内存),当你需要使用的物理内存不足时,也会出发OOM Killer. 内核代码为:common/mm/oom_kill.c. 其调用栈为:
malloc()
_alloc_pages()
out_of_memory()
select_bad_process()
oom_kill_process()
common/mm/oom_kill.c
int sysctl_panic_on_oom;
int sysctl_oom_kill_allocating_task;
bool out_of_memory(struct oom_control *oc)
{
...
constraint = constrained_alloc(oc);
if (constraint != CONSTRAINT_MEMORY_POLICY)
oc->nodemask = NULL;
check_panic_on_oom(oc, constraint);
if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&
current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) &&
current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
get_task_struct(current);
oc->chosen = current;
oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");
return true;
}
select_bad_process(oc);
if (oc->chosen && oc->chosen != (void *)-1UL) {
oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
"Memory cgroup out of memory");
schedule_timeout_killable(1);
}
...
}
Kill 策略:
1)计算该进程以及其子进程所占用的内存;
2)计算CPU时间和存活时间
3)计算进程优先级等/proc/pids/oom_score,/proc/pids/oom_adj;
占用内存越高,得分越高,cpu时间和存活时间越高,得分越低;进程优先级越高,得分越低
综合上述因素后,会得到一个point,得分最高的会被选中,然后被kill掉
LMK
基于 Linux Kernel 的 OOMKiller 思想,Android 系统拓展出了自己的内存监控体系 : Low Memory Killer.相比 Linux 达到临界值才触发,Android 实现了不同梯级的 Killer。
横向比较oomkiller Vs lmk: OOMkiller(Out Of Memory Killer)是在Linux系统无法分配新内存的时候,选择性杀掉进程,到oom的时候,系统可能已经不太稳定,而LowMemoryKiller是一种根据内存阈值级别触发的内存回收的机制,在系统可用内存较低时,就会选择性杀死进程的策略,相对OOMKiller,更加灵活。
先来看看一下两个文件节点:
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
我们可以修改以上两个文件节点的值:
write /sys/module/lowmemorykiller/parameters/adj 0, 8
write /sys/module/lowmemorykiller/parameters/minfree 1024, 4096
取值说明:当系统可用内存低于4096个pages时,则会杀掉oom_score_adj>=8的进程
ActivityManagerService 在运行时会根据当前的系统配置自动调整 adj 和 minfree. AMS.updateOomLevels 方法也是通过修改上面两个文件来实现的。
console:/
OOM levels:
-900: SYSTEM_ADJ ( 73,728K)
-800: PERSISTENT_PROC_ADJ ( 73,728K)
-700: PERSISTENT_SERVICE_ADJ ( 73,728K)
0: FOREGROUND_APP_ADJ ( 73,728K)
100: VISIBLE_APP_ADJ ( 92,160K)
200: PERCEPTIBLE_APP_ADJ ( 110,592K)
250: PERCEPTIBLE_LOW_APP_ADJ ( 129,024K)
300: BACKUP_APP_ADJ ( 147,456K)
400: HEAVY_WEIGHT_APP_ADJ ( 147,456K)
500: SERVICE_ADJ ( 147,456K)
600: HOME_APP_ADJ ( 147,456K)
700: PREVIOUS_APP_ADJ ( 147,456K)
800: SERVICE_B_ADJ ( 147,456K)
900: CACHED_APP_MIN_ADJ ( 147,456K)
999: CACHED_APP_MAX_ADJ ( 184,320K)
Process OOM control (8 total, non-act at 0, non-svc at 0):
PERS
oom: max=-900 curRaw=-900 setRaw=-900 cur=-900 set=-900
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
PERS
oom: max=-800 curRaw=-800 setRaw=-800 cur=-800 set=-800
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
PERS
oom: max=-800 curRaw=-800 setRaw=-800 cur=-800 set=-800
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
PERS
oom: max=-800 curRaw=-800 setRaw=-800 cur=-800 set=-800
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
PERS
oom: max=-800 curRaw=-800 setRaw=-800 cur=-800 set=-800
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
Proc
com.android.bluetooth/.btservice.AdapterService<=Proc{511:system/1000}
oom: max=1001 curRaw=-700 setRaw=-700 cur=-700 set=-700
state: cur=PER set=PER lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=true hasAboveClient=false
Proc
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
Proc
android.ext.services/.autofill.InlineSuggestionRenderServiceImpl<=Proc{511:system/1000}
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
state: cur=BFGS set=BFGS lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=true hasAboveClient=false
mHomeProcess: ProcessRecord{74a3758 897:com.android.tv.settings/1000}
mPreviousProcess: null
console:/
ADJ优先级 | 优先级 | 优先级 |
---|
UNKNOWN_ADJ | 16 | 一般指将要会缓存进程,无法获取确定值 | CACHED_APP_MAX_ADJ | 15 | 不可见进程的adj最大值(不可见进程可能在任何时候被杀死) | CACHED_APP_MIN_ADJ | 9 | 不可见进程的adj最小值(不可见进程可能在任何时候被杀死) | SERVICE_B_AD | 8 | B List中的Service(较老的、使用可能性更小) | PREVIOUS_APP_ADJ | 7 | 上一个App的进程(比如APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ) | HOME_APP_ADJ | 6 | Home进程 | SERVICE_ADJ | 5 | 服务进程(Service process) | HEAVY_WEIGHT_APP_ADJ | 4 | 后台的重量级进程,system/rootdir/init.rc文件中设置 | BACKUP_APP_ADJ | 3 | 备份进程(这个不太了解) | PERCEPTIBLE_APP_ADJ | 2 | 可感知进程,比如后台音乐播放 | VISIBLE_APP_ADJ | 1 | 可见进程(可见,但是没能获取焦点,比如新进程仅有一个悬浮Activity,Visible process) | FOREGROUND_APP_ADJ | 0 | 前台进程(正在展示是APP,存在交互界面,Foreground process) | PERSISTENT_SERVICE_ADJ | -11 | 关联着系统或persistent进程 | PERSISTENT_PROC_ADJ | -12 | 系统persistent进程,比如telephony | SYSTEM_ADJ | -16 | 系统进程 | NATIVE_ADJ | -17 | native进程(不被系统管理) |
从Android 7.0开始,ADJ采用100、200、300;在这之前的版本ADJ采用数字1、2、3,这样的调整可以更进一步地细化进程的优先级,比如在VISIBLE_APP_ADJ(100)与PERCEPTIBLE_APP_ADJ(200)之间,可以有ADJ=101、102级别的进程。
Android应用的优先级是如何更新的 在Android家族,5.0之前的系统进程优先级是AMS进程直接修改的(通过了Linux中的一个proc文件体统), 5.0之后,是修改优先级的操作被封装成了一个独立的服务-lmkd,lmkd服务位于用户空间,其作用层次同AMS、WMS类似,就是一个普通的系统服务(其实就是AMS通过socket向lmkd服务发送请求,让lmkd去更新进程的优先级)
AMS 更新进程优先级: 1)AMS.applyOomAdjLocked(),会设置某个进程的adj; 2)AMS.updateConfiguration(),会更新整个各个级别的oom_adj信息. 3)AMS.cleanUpApplicationRecordLocked()或者handleAppDiedLocked(),会将某个进程从lmkd策略中移除.
lmkd:
system/core/rootdir/init.rc
start lmkd
system/memory/lmkd/lmkd.rc
system/memory/lmkd/lmkd.c
system/memory/lmkd/lmkd.c
static void cmd_procprio(LMKD_CTRL_PACKET packet) {
struct proc *procp;
char path[80];
char val[20];
int soft_limit_mult;
struct lmk_procprio params;
lmkd_pack_get_procprio(packet, ¶ms);
if (params.oomadj < OOM_SCORE_ADJ_MIN ||
params.oomadj > OOM_SCORE_ADJ_MAX) {
ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
return;
}
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
snprintf(val, sizeof(val), "%d", params.oomadj);
writefilestring(path, val);
if (use_inkernel_interface)
return;
...
}
static int kill_one_process(struct proc* procp, int min_score_adj,
enum vmpressure_level level) {
...
r = kill(pid, SIGKILL);
ALOGI(
"Killing '%s' (%d), uid %d, adj %d\n"
" to free %ldkB because system is under %s memory pressure oom_adj %d\n",
taskname, pid, uid, procp->oomadj, tasksize * page_k,
level_name[level], min_score_adj);
pid_remove(pid);
...
}
LomemoryKiller 杀死进程(内核) shrinker LMK驱动通过注册shrinker来实现的,shrinker是linux kernel标准的回收内存page的机制,由内核线程kswapd负责监控。当内存不足时kswapd线程会遍历一张shrinker链表,并回调已注册的shrinker函数来回收内存page,kswapd还会周期性唤醒来执行内存操作。
common/drivers/staging/android/lowmemorykiller.c
static struct shrinker lowmem_shrinker = {
.scan_objects = lowmem_scan,
.count_objects = lowmem_count,
.seeks = DEFAULT_SEEKS * 16
};
static int __init lowmem_init(void) {
register_shrinker(&lowmem_shrinker);
return 0;
}
static void __exit lowmem_exit(void) {
unregister_shrinker(&lowmem_shrinker);
}
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
struct task_struct *tsk;
struct task_struct *selected = NULL;
unsigned long rem = 0;
int tasksize;
int i;
short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
int minfree = 0;
int selected_tasksize = 0;
short selected_oom_score_adj;
int array_size = ARRAY_SIZE(lowmem_adj);
int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
int other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM) -
total_swapcache_pages();
if (lowmem_adj_size < array_size)
array_size = lowmem_adj_size;
if (lowmem_minfree_size < array_size)
array_size = lowmem_minfree_size;
for (i = 0; i < array_size; i++) {
minfree = lowmem_minfree[i];
if (other_free < minfree && other_file < minfree) {
min_score_adj = lowmem_adj[i];
break;
}
}
if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
return 0;
}
selected_oom_score_adj = min_score_adj;
rcu_read_lock();
for_each_process(tsk) {
struct task_struct *p;
short oom_score_adj;
if (tsk->flags & PF_KTHREAD)
continue;
p = find_lock_task_mm(tsk);
if (!p)
continue;
if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
time_before_eq(jiffies, lowmem_deathpending_timeout)) {
task_unlock(p);
rcu_read_unlock();
return 0;
}
oom_score_adj = p->signal->oom_score_adj;
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}
tasksize = get_mm_rss(p->mm);
task_unlock(p);
if (tasksize <= 0)
continue;
if (selected) {
if (oom_score_adj < selected_oom_score_adj)
continue;
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
selected = p;
selected_tasksize = tasksize;
selected_oom_score_adj = oom_score_adj;
lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
p->comm, p->pid, oom_score_adj, tasksize);
}
if (selected) {
long cache_size = other_file * (long)(PAGE_SIZE / 1024);
long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
long free = other_free * (long)(PAGE_SIZE / 1024);
lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \ ...);
lowmem_deathpending_timeout = jiffies + HZ;
set_tsk_thread_flag(selected, TIF_MEMDIE);
send_sig(SIGKILL, selected, 0);
rem += selected_tasksize;
}
rcu_read_unlock();
return rem;
}
参考: LowMemoryKiller原理 Android进程优先级ADJ算法 Android LowMemoryKiller原理分析 Android 内存管理之LowMemoryKiller实现原理分析
|