Android OOM、OOMKillery以及LMK相关概念
时间:2022-10-31 14:30:00
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(); // manafest.xml android:largeHeap="true"
程序发生 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: [ ] oom_kill_process+0x241/0x390
Mar 10 23:40:13 ...... kernel: [ ] ? 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;//内存不够时内核是否直接panic
int sysctl_oom_kill_allocating_task;//是否选择当前正在申请内存的进程进行kill
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:/ # dumpsys activity o
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 # 7: sys F/ /PER LCM t: 0 511:system/1000 (fixed)
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 # 5: pers F/ /PER LCM t: 0 683:com.android.systemui/u0a39 (fixed)
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 # 4: pers F/ /PER LCM t: 0 785:com.droidlogic/1000 (fixed)
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 # 3: pers F/ /PER LCM t: 0 872:com.android.se/1068 (fixed)
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 # 2: pers F/ /PER LCM t: 0 851:com.android.networkstack.process/1073 (fixed)
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 # 6: psvc F/ /PER LCM t: 0 664:com.android.bluetooth/1002 (service)
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 # 1: fg T/A/TOP LCM t: 0 897:com.android.tv.settings/1000 (top-activity)
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 # 0: vis F/ /BFGS --- t: 0 922:android.ext.services/u0a43 (service)
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; //遍历lowmem_minfree数组找出相应的最小adj值 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; //小于目标adj的进程,则忽略 if (oom_score_adj < min_score_adj) { task_unlock(p); continue; } //获取的是进程的Resident Set Size,也就是进程独占内存 + 共享库大小。 tasksize = get_mm_rss(p->mm); task_unlock(p); if (tasksize <= 0) continue; //算法关键,选择oom_score_adj最大的进程中,并且rss内存最大的进程. 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