部分内容出至林学森的Android内核设计思想。
Android官网内存管理
部分出至https://www.jianshu.com/p/94d1cd553c44
Android本质是Linux所以先从Linux说起。
Linux
Linux的内存管理为系统中所有的task提供可靠的内存分配、释放和保护机制。
核心:
虚拟内存
内存分配与释放
内存保护
虚拟内存思想:
将外存储器的部分空间作为内存的扩展,如从硬盘划出4GB大小。
当内存资源不足时,系统按照一定算法自动条形优先级低的数据块,并把他们存储到硬盘中。
后续如果需要用到硬盘中的这些数据块,系统将产生“缺页”指令,然后把他们交换回内存中。
这些都是由操作系统内核自动完成的,对上层应用”完全透明“。
虚拟内存机制:
- 逻辑地址(Logical Address,相对地址)
是程序编译后产生的地址,两部分组成:段选择子(Segment Selector)、偏移值(Offset)
-
段选择子:描述逻辑地址所处的段,16bit
TL: Table Indicator,区分表,就是GDT(Global Descriptor Table)及LDT(Local Descriptor Table),0表示GDT,1表示LDT
(GDT和LDT:描述各种段描述符,而表本身的存储地址则由GDTR和LDTR两个CPU寄存来保存。GDT的有效范围是全局的,LDT个进程自己创建增加额外的段)
偏移值:描述段内的偏移值,32bit
- 线性地址
线性地址是逻辑地址经过分段机制转换后形成的;由页基址和偏移地址(页内偏移量)组成。
思想:根据段选择子中的TL自动,得知段描述的存储,经过GDTR或者LDTR获得GDT或者LDT的存储地址。根据段选择子中的INDEX字段,到GDT或者LDT中查找到对应的段描述符。根据段描述符获得此段的基地址。由基地址+段内偏移值得到线性地址。 - 物理地址
机器真实的物理内存地址空间范围。
比如64KB内存的系统,物理地址范围是0x0000~0xFFFF。 - 段页式内存管理
逻辑地址——>分段机制转换——>线性地址——>分页机制是否开启——>分页机制转换——>物理地址
不是所有系统都支持段页式内存管理。有些系统只提供页式管理机制,Linux内存理论上是段页式,也只实现了分页机制(分段机制只用到了一部分功能)。
页:分页机制的操作对象,是固定大小的内存块。一般情况下4KB大小。
页框:对物理内存的最小操作单位。页和页框的大小必须完全一致。 - 虚拟内存
当前与物理内存没有映射关系的页,访问时回产生缺页中断,操作系统自动介入处理,利用一定的算法将当前不常用的页调出内存,从而为缺失页腾出位置,然后将缺失页从外存储器重新取回,最后返回中断点继续操作。
内存保护:
每个进程的逻辑地址和物理地址都不是直接对应的,任何进程都没办法访问到它管辖范围外的内存空间——即刻意产生的内存越界与非法访问,操作系统也会马上阻止并强行关闭程序,从而有力的保障应用程序和操作系统的安全和稳定。
内存分配与回收
- 保证硬件无关性
每个硬件平台的物理内存型号、大小甚至架构(比如不同的体系结构)等都可能是不一样的。要尽可能实现向上的“透明”。 - 动态分配内存和回收
如何为内存划分不同的使用区域:分配的粒度问题,即分配 的最小单位;如何管理和区别已使用和未使用的内存;如何回收和再利用。
Native(C/C++):栈上直接分配出栈后自动释放,堆上分配函和释放数包括malloc/free、new/delete。
Java:java堆,方法区(包含运行时常量池),java虚拟机栈、本地方法栈这些都会涉及内存分配。垃圾回收器负责回收释放内存,主要是java堆和方法区;java虚拟机栈和本地方法栈基本是出栈后释放,也取决于虚拟机的具体实现。 - 内存碎片
6块内存,连续使用了5块,其中第二块欸回收释放后;就会形成两个不连续的内存块。
Linux OOMKiller即内存监控机制
一旦发现系统的可用内存达到临界值,机会按照优先级顺序,匆匆低到高逐步杀掉进程,回收内存。
存储位置:/proc/
优先级策略:
进程消耗的内存
进程占用的CPU时间
oom_adj(OOM权重)
Android
Android平台运行的前提是可用内存是浪费的内存。它试图在任何时候使用所有可用的内存。例如,系统会在APP关闭后将其保存在内存中,以便用户可以快速切换回它们。出于这个原因,Android设备通常运行时只有很少的空闲内存。在重要系统进程和许多用户应用程序之间正确分配内存内对存管理是至关重要。
Android有两种主要的机制来处理低内存的情况:内核交换守护进程(kernel swap daemon)和低内存杀手(low-memory killer)。
切换APP
当用户在APP之间切换时,Android会在最近使用的(LRU)缓存中保留不在前台的APP,即用户看不到的APP,或运行类似音乐播放的前台服务。如果用户稍后返回APP,系统将重用该进程,从而使APP切换更快。
如果你的APP有一个缓存进程,并且它保留了当前不需要的内存,那么即使用户不使用它,你的APP也会影响系统的整体性能。由于系统内存不足,它会从最近使用最少的进程开始杀死LRU缓存中的进程。该系统还负责处理占用最多内存的进程,并可以终止这些进程以释放RAM。
当系统开始终止LRU缓存中的进程时,它主要是自底向上工作的。系统还考虑哪些进程消耗更多的内存,从而在终止时为系统提供更多的内存增益。你在LRU列表中消耗的内存越少,你就越有可能留在列表中并能够快速恢复。
内存共享(Share memory)
为了满足RAM的所有需求,Android尝试共享RAM来跨进程通信。它可以做到以下方式:
- 每个APP都是从一个叫做Zygote的现有进程中fork来的。当系统启动并加载通用framework代码和资源(如Activity themes)时,Zygote进程开启。系统fork Zygote进程后启动新进程,在新进程中加载并运行APP的代码。这种方法允许分配给框架代码和资源的大部分RAM页在所有APP进程中共享。
- 大多数静态数据都映射到一个进程中。这种技术允许在进程之间共享数据,并允许在需要时将其调出。静态数据示例包括:Dalvik代码(通过将其放置在预链接的.odex文件中进行直接mmapping)、APP资源(通过将资源表设计为可以mmapped和对齐的zip实体APK的结构)和传统项目元素(如.so文件中的native代码)。
- 在许多地方,Android使用显式分配的共享内存区域(ashmem或gralloc)来跨进程共享相同的动态RAM。例如,window surfaces在app和屏幕合成器之间使用共享内存,而光标缓冲区在content provider和client之间使用共享内存。
由于共享内存的广泛使用,确定app使用的内存量需要小心。在调查RAM使用情况时,需要使用适当的技术。
内存类型
Android设备包含三种不同类型的内存:RAM、zRAM和storage。
注意:CPU和GPU都访问同一个RAM。
- RAM是最快的内存类型,但通常大小有限。高端设备通常具有更大的RAM容量。
- zRAM是用于交换空间的RAM分区。所有内容在放入zRAM时压缩,然后在从zRAM复制时解压缩。当页面移入或移出zRAM时,RAM的这一部分的大小会增大或减小。设备制造商可以设置最大尺寸。
-
Storage包含所有持久性数据,如文件系统和所有app、库和平台所包含的代码。存储器的容量比其他两种存储器大得多。在Android上,存储不像在其他Linux实现中那样用于交换空间,因为频繁的写入会导致内存磨损,并缩短存储介质的寿命。
内存页(Memory Pages)
内存被拆分成页。通常每页有4KB的内存。
页面被认为是空闲的或已使用的。
空闲页是未使用的RAM。
已使用页是系统正在积极使用的RAM,分为以下类别:
- Cached(缓存):支持文件存储的内存(例如,代码或内存映射文件)。有两种类型的缓存:
- Private(私有):由一个进程拥有且不共享
Clean(干净):存储的是未修改的一个文件副本,可以由kswapd删除以增加可用内存
Dirty(脏):存储的是已修改的一个文件副本;可以通过kswapd移动到或压缩到zRAM以增加可用内存 - Shared(共享):多个进程使用
Clean(干净):存储的是未修改的一个文件副本,可以由kswapd删除以增加可用内存
Dirty(脏):存储的是已修改的一个文件副本;允许回写存储器上的文件以增加可用内存通过kswapd或者显式使用msync()或munmap()。
- Private(私有):由一个进程拥有且不共享
- Anonymous( 匿名的):不支持一个文件存储的内存(例如,由mmap()分配,并设置了MAP_ANONYMOUS标志)
Dirty(脏):通过kswapd移动或者压缩到zRAM中以增加可用内存
干净的页面(Clean pages)包含一个文件(或文件的一部分)的一份精确副本存在存储器上。当一个干净的页面不再包含一个精确的文件副本(例如,来自应用程序操作的结果)时,它就变成了脏页。可以删除干净的页,因为它们始终可以使用存储中的数据重新生成;不能删除脏页(Dirty pages),否则数据将丢失。
计算内存占用
内核跟踪系统中的所有内存页。
当确定一个应用程序正在使用多少内存时,系统必须考虑shared pages。APP访问相同的服务或库将可能共享内存页。例如,Google Play Services 和一个游戏APP可能共享一个位置服务。这使得很难确定有多少内存属于这个服务相对于每个APP。
要确定APP的内存占用,可以使用以下指标:
- Resident Set Size(常驻集大小 RSS):APP使用的共享和非共享页面数量
- Proportional Set Size(比例集大小 PSS):APP使用的非共享页面的数量和均匀分布的共享页面数量(例如,如果三个进程共享3MB,则每个进程在PSS中获得1MB)
- Unique Set Size(唯一集大小 USS):APP使用的非共享页面数(不包括共享页面)
当操作系统想要知道所有进程使用了多少内存时,PSS非常有用,因为页面不会被多次计数。PSS需要很长时间来计算,因为系统需要确定哪些页面是共享的,以及被有多少进程。RSS不区分共享页面和非共享页面(使计算速度更快),更适合于跟踪内存分配的更改。
内核交换守护程序(kernel swap daemon)
内核交换守护进程(kswapd)是Linux内核的一部分,它将使用过的内存转换为空闲内存。当设备上的空闲内存不足时,守护进程将变为活动状态。Linux内核保持低和高的可用内存阈值。当空闲内存低于低阈值时,kswapd开始回收内存。当空闲内存达到高阈值,kswapd将停止回收内存。
kswapd可以通过删除干净的页面来回收干净的页面,因为它们有存储器支持并且没有被修改。如果进程试图寻址已删除的干净页,则系统会将该页从存储器复制到RAM。此操作称为请求分页。
kswapd将缓存的私有脏页(private dirty pages)和匿名脏页(anonymous dirty pages)移动到zRAM进行压缩。这样做可以释放RAM中的可用内存(空闲页)。如果进程试图触摸zRAM中脏页,则该页将被解压缩并移回RAM。如果与压缩页关联的进程被终止,则该页将从zRAM中删除。
如果可用内存量低于某个阈值,系统将开始终止进程。
lmkd实现源码要在system/core/lmkd/lmkd.c。
lmkd会创建名为lmkd的socket,节点位于/dev/socket/lmkd,该socket用于跟上层framework交互。
service lmkd /system/bin/lmkd
class core
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
- lmkd 会接收 Framework 的命令,进行相应的操作:
功能 命令 对应方法 LMK_PROCPRIO 设置进程adj PL.setOomAdj() LMK_TARGET 更新oom_adj PL.updateOomLevels() LMK_PROCREMOVE 移除进程 PL.remove() - lmkd socket 命令处理
static void ctrl_command_handler(void) { int ibuf[CTRL_PACKET_MAX / sizeof(int)]; int len; int cmd = -1; int nargs; int targets; len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); if (len <= 0) return; nargs = len / sizeof(int) - 1; if (nargs < 0) goto wronglen; //将网络字节顺序转换为主机字节顺序 cmd = ntohl(ibuf[0]); switch(cmd) { case LMK_TARGET: targets = nargs / 2; if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) goto wronglen; cmd_target(targets, &ibuf[1]); break; case LMK_PROCPRIO: if (nargs != 3) goto wronglen; //设置进程adj cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); break; case LMK_PROCREMOVE: if (nargs != 1) goto wronglen; cmd_procremove(ntohl(ibuf[1])); break; default: ALOGE("Received unknown command code %d", cmd); return; } return; wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); }
- 设置进程 adj
向节点/proc/static void cmd_procprio(int pid, int uid, int oomadj) { struct proc *procp; char path[80]; char val[20]; ... snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); snprintf(val, sizeof(val), "%d", oomadj); // 向节点/proc/
/oom_score_adj写入oomadj writefilestring(path, val); // 当使用kernel方式则直接返回 if (use_inkernel_interface) return; procp = pid_lookup(pid); if (!procp) { procp = malloc(sizeof(struct proc)); if (!procp) { // Oh, the irony. May need to rebuild our state. return; } procp->pid = pid; procp->uid = uid; procp->oomadj = oomadj; proc_insert(procp); } else { proc_unslot(procp); procp->oomadj = oomadj; proc_slot(procp); } } /oom_score_adj写入oom_adj。由于use_inkernel_interface=1,那么再接下里需要看看 kernel 的情况。use_inkernel_interface该值后续应该会逐渐采用用户空间策略。
小结:
LMK_TARGET:AMS.updateConfiguration()
的过程中调用updateOomLevels()
方法, 分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应信息;
LMK_PROCPRIO:AMS.applyOomAdjLocked()
的过程中调用setOomAdj()
向/proc/
LMK_PROCREMOVE:AMS.handleAppDiedLocked
或者 AMS.cleanUpApplicationRecordLocked()
的过程,调用remove(),目前不做任何事,直接返回;
onTrimMemory()
为了进一步帮助平衡系统内存并避免终止APP进程,可以Activity类中实现ComponentCallbacks2接口。提供的onTrimMemory()回调方法允许APP在前台或后台侦听与内存相关的事件,然后释放对象以响应应用程序生命周期或表明系统需要回收内存的系统事件。
onTrimMemory()回调是在Android 4.0(API级别14)中添加的。
对于早期版本,可以使用onLowMemory(),它大致相当于TRIM_MEMORY_COMPLETE事件。
import android.content.ComponentCallbacks2;
// Other import statements ...
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code ...
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/
public void onTrimMemory(int level) {
// Determine which lifecycle or system event was raised.
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*用户界面进入后台
Release any UI objects that currently hold memory.
The user interface has moved to the background.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/* APP正在运行
Release any memory that your app doesn't need to run.
The device is running low on memory while the app is running.
The event raised indicates the severity of the memory-related event.
If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
begin killing background processes.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/* APP在后台
Release as much memory as the process can.
The app is on the LRU list and the system is running low on memory.
The event raised indicates where the app sits within the LRU list.
If the event is TRIM_MEMORY_COMPLETE, the process will be one of
the first to be terminated.
*/
break;
default:
/* 一般消息释放非关键数据
Release any non-critical data structures.
The app received an unrecognized memory level value
from the system. Treat this as a generic low-memory message.
*/
break;
}
}
}
Low-memory killer
一个专门的驱动。(Linux Kernel 4.12 已移除交给kswapd处理)。
很多时候,kswapd无法为系统释放足够的内存。在这种情况下,系统使用onTrimMemory()通知APP内存不足,应该减少其分配。如果这还不够,内核将开始终止进程以释放内存,它使用低内存杀手(LMK)来完成这个任务。
为了决定要终止哪个进程,LMK使用一个名为oom_adj_score的“out of memory”分数来确定运行进程的优先级,高分的进程首先被终止。
后台应用程序首先被终止,系统进程最后被终止。
下表列出了从高到低的LMK评分类别。第一排得分最高的项目将首先被杀死:
以下是上表中各种类别的说明:
Background apps: 以前运行但当前未处于活动状态的APP。LMK将首先杀掉后台APP,从最高分的那个开始。
Previous app:最近使用的后台APP。前一个APP有更高的优先级(分数)比后台APP,因为用户更有可能切换到它,而不是后台APP中的一个。
Home app:这是launcher APP。杀死它会让墙纸消失。
Services:服务由APP启动,可能包括同步或上传到云。
Perceptible apps:在某种程度上用户可感知的非前台APP,如显示小用户界面的正在运行的搜索进程或正在收听音乐。
Foreground app:当前正在使用的APP。关闭前台APP看起来像是APP崩溃,可能会向用户表明设备出了问题。
Persistent (services):这些是设备的核心服务,如电话和wifi。
System:系统进程。当这些进程被终止时,手机可能会重新启动。
Native:系统使用的非常低级的进程(例如,kswapd)。
设备制造商可以改变LMK的行为。
Linux OOMKiller即内存监控机制,一旦发现系统的可用内存达到临界值,机会按照优先级顺序,匆匆低到高逐步杀掉进程,回收内存。
OOMKiller相应的源码文件:
/fs/proc/base.c
/mm/oom_kill.c
存储位置:/proc//oom_score
优先级策略:
进程消耗的内存
进程占用的CPU时间
oom_adj(OOM权重)-
LowMemoryKiller
存储位置:drivers/staging/android/Lowmemorykiller.cstatic struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; static int __init lowmem_init(void) { register_shrinker(&lowmem_shrinker); vmpressure_notifier_register(&lmk_vmpr_nb); return 0; } static void __exit lowmem_exit(void) { unregister_shrinker(&lowmem_shrinker); }
通过
register_shrinker
和unregister_shrinker
分别用于初始化和退出。shrinker
LMK驱动通过注册 shrinker 来实现的,shrinker是linux kernel标准的回收内存page的机制,由内核线程kswapd负责监控。
当内存不足时kswapd线程会遍历一张shrinker链表,并回调已注册的shrinker函数来回收内存page,kswapd还会周期性唤醒来执行内存操作。每个zone维护active_list和inactive_list链表,内核根据页面活动状态将page在这两个链表之间移动,最终通过shrink_slab和shrink_zone来回收内存页。lowmem_shrink
触发 shrink 操作:
选择oom_score_adj最大的进程中,并且rss内存最大的进程作为选中要杀的进程。
杀进程方式:send_sig(SIGKILL, selected, 0)向选中的目标进程发送signal 9来杀掉目标进程。
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) { struct task_struct *tsk; struct task_struct *selected = NULL; int rem = 0; int tasksize; int i; int ret = 0; short min_score_adj = OOM_SCORE_ADJ_MAX + 1; //1001 int minfree = 0; int selected_tasksize = 0; int selected_oom_score_adj; int array_size = ARRAY_SIZE(lowmem_adj); int other_free; int other_file; unsigned long nr_to_scan = sc->nr_to_scan; if (nr_to_scan > 0) { if (mutex_lock_interruptible(&scan_mutex) < 0) return 0; } // 剩余内存 other_free = global_page_state(NR_FREE_PAGES); if (global_page_state(NR_SHMEM) + total_swapcache_pages < global_page_state(NR_FILE_PAGES)) other_file = global_page_state(NR_FILE_PAGES) - global_page_state(NR_SHMEM) - total_swapcache_pages; else other_file = 0; tune_lmk_param(&other_free, &other_file, sc); 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 (nr_to_scan > 0) { ret = adjust_minadj(&min_score_adj); lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd\n", nr_to_scan, sc->gfp_mask, other_free, other_file, min_score_adj); } rem = global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); if (nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX + 1) { lowmem_print(5, "lowmem_shrink %lu, %x, return %d\n", nr_to_scan, sc->gfp_mask, rem); if (nr_to_scan > 0) mutex_unlock(&scan_mutex); if ((min_score_adj == OOM_SCORE_ADJ_MAX + 1) && (nr_to_scan > 0)) trace_almk_shrink(0, ret, other_free, other_file, 0); return rem; } selected_oom_score_adj = min_score_adj; rcu_read_lock(); for_each_process(tsk) { struct task_struct *p; int oom_score_adj; if (tsk->flags & PF_KTHREAD) continue; /* if task no longer has any memory ignore it */ if (test_task_flag(tsk, TIF_MM_RELEASED)) continue; if (time_before_eq(jiffies, lowmem_deathpending_timeout)) { if (test_task_flag(tsk, TIF_MEMDIE)) { rcu_read_unlock(); /* give the system time to free up the memory */ msleep_interruptible(20); mutex_unlock(&scan_mutex); return 0; } } p = find_lock_task_mm(tsk); if (!p) continue; oom_score_adj = p->signal->oom_score_adj; // oom_adj 小于 最小值,忽略 if (oom_score_adj < min_score_adj) { task_unlock(p); continue; } // 进程 RSS 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(3, "select '%s' (%d), adj %hd, size %d, to kill\n", p->comm, p->pid, oom_score_adj, tasksize); } if (selected) { lowmem_print(1, "Killing '%s' (%d), adj %d,\n" \ " to free %ldkB on behalf of '%s' (%d) because\n" \ " cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \ " Free memory is %ldkB above reserved.\n" \ " Free CMA is %ldkB\n" \ " Total reserve is %ldkB\n" \ " Total free pages is %ldkB\n" \ " Total file cache is %ldkB\n" \ " Slab Reclaimable is %ldkB\n" \ " Slab UnReclaimable is %ldkB\n" \ " Total Slab is %ldkB\n" \ " GFP mask is 0x%x\n", selected->comm, selected->pid, selected_oom_score_adj, selected_tasksize * (long)(PAGE_SIZE / 1024), current->comm, current->pid, other_file * (long)(PAGE_SIZE / 1024), minfree * (long)(PAGE_SIZE / 1024), min_score_adj, other_free * (long)(PAGE_SIZE / 1024), global_page_state(NR_FREE_CMA_PAGES) * (long)(PAGE_SIZE / 1024), totalreserve_pages * (long)(PAGE_SIZE / 1024), global_page_state(NR_FREE_PAGES) * (long)(PAGE_SIZE / 1024), global_page_state(NR_FILE_PAGES) * (long)(PAGE_SIZE / 1024), global_page_state(NR_SLAB_RECLAIMABLE) * (long)(PAGE_SIZE / 1024), global_page_state(NR_SLAB_UNRECLAIMABLE) * (long)(PAGE_SIZE / 1024), global_page_state(NR_SLAB_RECLAIMABLE) * (long)(PAGE_SIZE / 1024) + global_page_state(NR_SLAB_UNRECLAIMABLE) * (long)(PAGE_SIZE / 1024), sc->gfp_mask); if (lowmem_debug_level >= 2 && selected_oom_score_adj == 0) { show_mem(SHOW_MEM_FILTER_NODES); dump_tasks(NULL, NULL); show_mem_call_notifiers(); } lowmem_deathpending_timeout = jiffies + HZ; send_sig(SIGKILL, selected, 0); set_tsk_thread_flag(selected, TIF_MEMDIE); rem -= selected_tasksize; rcu_read_unlock(); /* give the system time to free up the memory */ msleep_interruptible(20); trace_almk_shrink(selected_tasksize, ret, other_free, other_file, selected_oom_score_adj); } else { trace_almk_shrink(1, ret, other_free, other_file, 0); rcu_read_unlock(); } lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n", nr_to_scan, sc->gfp_mask, rem); mutex_unlock(&scan_mutex); return rem; }
-
lmkd参数
oom_score -- 是该进程的最终得分,分数越高,越容易被杀死;
oom_score_adj -- 范围:[-1000, 1000],是kernel用来配置进程优先级的。值越低,最终的oom_score越低。
oom_adj-- 是给用户来配置进程优先级的。为了方便用户配置,提供了范围更小[-17,15],数字越小优先级越高,-17表示该进程被保护,不被OOMKiller杀死。
lowmem_oom_adj_to_oom_score_adj 计算:
当oom_adj = 15, 则 oom_score_adj = 1000;
当oom_adj < 15, 则 oom_score_adj = oom_adj * 1000/17;#define OOM_DISABLE (-17) #define OOM_SCORE_ADJ_MAX 1000 static int lowmem_oom_adj_to_oom_score_adj(int oom_adj) { if (oom_adj == OOM_ADJUST_MAX) return OOM_SCORE_ADJ_MAX; else return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; }
从上面可以看到,这个算法是有损的,所以,有时候会发现读出的oom_adj的值同设置得不一样了。如,设置oom_adj =13, 那么 oom_score_adj=764,再次读oom_adj, 764 * 17 / 1000 == 12了。
在Android系统里面,当app的状态发生变化,如:创建,收到广播,唤醒,放入后台等, ActivityManagerService的updateOomAdjLocked()
调用computeOomAdjLocked()
,然后通过applyOomAdjLocked()
把app的oom_adj值写入到Linux Kernel (/proc//oom_score_adj)。
AMS根据app进程的特性进行了分类,不同的类别,对应不同的数值。ADJ Description HIDDEN_APP_MAX_ID=15 当前只运行了不可见的Activity组件的进程 HIDDEN_APP_MIN_ID=9 当前只运行了不可见的Activity组件的进程 SERVICE_B_ADJ=8 B list of service。和A list相比,他们对用户的黏合度要小一些,长时间未使用的service进程。 PREVIOUS_APP_ADJ=7 用户前一次交互的进程。按照用户的使用习惯,人们经常会在几个常用进程间切换,所以这类进程得到再次运行的概率比较大 HOME_APP_ADJ=6 Launcher进程,它对于用户的重要性不言而喻 SERVICE_ADJ=5 当前运行了application service的进程 HEAVY_WEIGHT_APP_ADJ=4 重量级APP进程,P以上版本才能配置: android:cantSaveState="true" BACKUP_APP_ADJ=3 用于承载backup相关操作的APP进程 PERCEPTIBLE_APP_ADJ=2 这类进程能被用户感觉到但不可见,如后台运行的音乐播放器 VISIBLE_APP_ADJ=1 前台APP启动的一些可见的组件的进程,如: 弹出的Email activity FOREGROUND_APP_ADJ=0 当前正在前台运行的进程,也就是用户正在交互的那个程序 PERSISTENT_PROC_ADJ=-12 Persistent性质的进程,如电话、短信、热点、wifi SYSTEM_ADJ=-16 系统进程 NATIVE_ADJ=-17 native进程(不被系统管理) ActivityManagerService
三个核心方法:
updateOomAdjLocked
:更新adj,当目标进程为空,或者被杀则返回false;否则返回true;
computeOomAdjLocked
:计算adj,返回计算后RawAdj值;
applyOomAdjLocked
:应用adj,当需要杀掉目标进程则返回false;否则返回true。
updateOomAdjLocked
中会调用computeOomAdjLocked
和applyOomAdjLocked
-
各层方法路线:
- Activity
唯一没有直接调用AMS的方法,WindowProcessController.updateProcessInfo
走到PooledLambda.doInvoke
,应该是调用WindowProcessListener
的具体实现,写这段人叫人无语。
AS---ActivityStack ASS---ActivityStackSupervisor
ASS.realStartActivityLocked:
启动Activity
AS.resumeTopActivityInnerLocked:
恢复栈顶Activity
AS.finishCurrentActivityLocked:
结束当前Activity
AS.destroyActivityLocked:
摧毁当前Activity - Service
AS---ActiveServices
AS.realStartServiceLocked:
启动服务
AS.bindServiceLocked:
绑定服务(只更新当前app)
AS.unbindServiceLocked:
解绑服务 (只更新当前app)
AS.bringDownServiceLocked:
结束服务 (只更新当前app)
AS.sendServiceArgsLocked:
在bringup或则cleanup服务过程调用 (只更新当前app) - Broadcast
BQ---BroadcastQueue
BQ.processNextBroadcast:
处理下一个广播
BQ.processCurBroadcastLocked:
处理当前广播
BQ.deliverToRegisteredReceiverLocked:
分发已注册的广播 (只更新当前app) - ContentProvider
AMS.removeContentProvider:
移除provider
AMS.publishContentProviders:
发布provider (只更新当前app)
AMS.getContentProviderImpl:
获取provider (只更新当前app) - Process
ActivityManagerService(Context systemContext, ActivityTaskManagerService atm):
构造方法
AMS.setSystemProcess:
创建并设置系统进程
AMS.addAppLocked:
创建persistent进程
AMS.attachApplicationLocked:
进程创建后attach到system_server的过程;
AMS.trimApplications:
清除没有使用app
AMS.appDiedLocked:
进程死亡
AMS.killAllBackgroundProcesses:
杀死所有后台进程.即(ADJ>9或removed=true的普通进程)
AMS.killPackageProcessesLocked:
以包名的形式杀掉相关进程;
- Activity
-
总结
以上整个过程可以简单总结如下:- 系统 Framework 层根据不同组件声明周期或者进程状态通知AMS,按照进程类型,动态分配不同的 adj 值,并且在一定的时机会对所有进程的 adj 进行更新;
更新 adj 时,AMS会和 lmkd 守护进程进行socket 通信,设置/proc/pid/oom_score_adj;ActivityManagerService在运行时会根据系统的当前配置通知lmkd修正 lmk driver 的参数。 - LowMemoryKiller注册了shrinker(Linux Kernel的一个内存管理工具),当kernel需要回收内存时,会回调LowMemoryKiller的lowmem_shrink(),它先检查kernel 剩下多少内存,根据剩下的内存数量来匹配数组 lowmem_minfree[], 找到数组索引值,然后,再使用该索引值,从 lowmem_adj[]这个数组里面就得到目标oom_adj值会被转成oom_score_adj ,最终,在大于等于该目标oom_score_adj 的进程中,杀死拥有最大oom_score_adj 值的进程--send_sig(SIGKILL, selected, 0) 。
static int lowmen_adj[6] = { 0, 1, 6, 12}; static int lowmen_adj_size = 4; static int lowmen_minfree = { 3 * 512, /* 6MB */ 2 * 1024, 4 * 1024, 16 * 1024 }; 数组lowmen_adj最多6个元素(默认只定义了4个),表示可用内存容量处于“某层级”时需要被处理的adj值。 数组lowmen_minfree 表示对层级的描述,例如:当系统可用内存小于6MB时,killer需要清理adj值为0以下的进程。
- 当内存耗尽的时候,OOMKiller会调用 out_of_memory()来select_bad_process(), oom_score最大的值就是那个将要被杀死的bad process。 oom_badness()以oom_score_adj作为基础值,根据是否为管理员进程,占用的内存情况,来计算出最终的oom_score值,分值越高,越容易被杀死。
所以,后台应用被回收的问题,需要额外关注:
进程的生命周期及5大优先级分类
减小内存占用,在 trimmemory 时能及时释放内存
查询命令:脚本procrank cat /proc/42/oom_score cat /proc/42/oom_score_adj cat /proc/42/oom_adj
- 系统 Framework 层根据不同组件声明周期或者进程状态通知AMS,按照进程类型,动态分配不同的 adj 值,并且在一定的时机会对所有进程的 adj 进行更新;
如何修改:
-
LowMemoryKiller 的阈值的设定
主要保存在2个文件之中,分别是:
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
adj保存着当前系统杀进程的等级,minfree则是保存着对应的内存阀值。
查询命令:shamu:/ # cat /sys/module/lowmemorykiller/parameters/adj shamu:/ # cat /sys/module/lowmemorykiller/parameters/minfree
-
framework的config.xml
-1
0
config_....KbytesAbsolute:非-1的情况下,是绝对值, AN使用下面算法,得到实际数组值。for ( int i = 0 ; i < mOomAdj.length; i++) { mOomMinFree[ i ] = (int) ((float)minfree_abs * mOomMinFree[ i ] / mOomMinFree[ mOomAdj.length - 1 ] ); }
config_....KbytesAdjust:非0情况下, 直接在每个数组值上 += reserve_adj;
如:-512 ,表明每个数组值都减少512。 当然了在实际上开发过程中,也可以直接在这个函数里面打补丁,或者读取系统属性,通过属性来进行配置等等。 像MStar方案,就定义了两个属性来进行第三方的配置: ro.mstar.lmkd.minfree和ro.mstar.lmkd.adj
-
在init.rc中通过下面的语句来修改:
write /system/module/lowmemorykiller/parameters/adj 0,8 write /system/module/lowmemorykiller/parameters/minfree 1024,4096
ActivityManagerService在运行时会根据系统的当前配置自动修正adj和minfree,尽可能适配不同的硬件。AMS内部updateOomLevels函数也是通过上述方法实现。
-
-
Android进程所属adj值
- 和前面类似通过改写文件的方式,如init.rc中将PID值为1的进程(init)的adj值改为-16,以保证它不会被杀死。
on early-init write /proc/1/oom_adj-16
- AndroidManifest.xml中给application标签添加“android:persistent=true”,设置为常驻内存。
- 和前面类似通过改写文件的方式,如init.rc中将PID值为1的进程(init)的adj值改为-16,以保证它不会被杀死。
-
Linux和内核对应关系
Android Version API Level Linux Version in AOSP Header Version 1.5 Cupcake 3 (2.6.27) 1.6 Donut 4 (2.6.29) 2.6.18 2.0/1 Eclair 5-7 (2.6.29) 2.6.18 2.2.x Froyo 8 (2.6.32) 2.6.18 2.3.x Gingerbread 9, 10 (2.6.35) 2.6.18 3.x.x Honeycomb 11-13 (2.6.36) 2.6.18 4.0.x Ice Cream San 14, 15 (3.0.1) 2.6.18 4.1.x Jelly Bean 16 (3.0.31) 2.6.18 4.2.x Jelly Bean 17 (3.4.0) 2.6.18 4.3 Jelly Bean 18 (3.4.39) 2.6.18 4.4 Kit Kat 19, 20 (3.10) 2.6.18 5.x Lollipop 21, 22 (3.16.1) 3.14.0 6.0 Marshmallow 23 (3.18.10) 3.18.10 7.0 Nougat 24 3.18.48 4.4.0 4.4.1 7.1 Nougat 25 ? 4.4.1 8.0 Oreo 26 3.18.72 4.4.83 4.9.44 4.10.0 8.1 Oreo 27 3.18.70 4.4.88 4.9.56 4.10.0 9.0 Pie 28 4.4.146 4.9.118 4.14.61 4.15.0 10.0 Q 29 4.9.191 4.14.142 4.19.71 5.0.3
虚拟机
Android Runtime(ART)和Dalvik虚拟机使用分页(Paging)和内存映射(mmapping)来管理内存。应用程序通过分配新对象或触摸已映射页面来修改内存都将保留在RAM中,并且不能被调出。应用程序释放内存的唯一方式是垃圾收集器。
- 垃圾收集(垃圾回收器算法、Java虚拟机运行时数据区域)
一个托管内存环境(如ART或Dalvik虚拟机)保持跟踪每个内存分配。一旦确定程序不再使用一块内存,它就会将其释放回堆(heap)中,无需程序员的干预。在托管内存环境中回收未使用内存的机制称为垃圾回收。垃圾收集有两个目标:在程序中查找未来无法访问的数据对象;回收这些对象使用的资源。 - Android的内存堆是分代的
意味着它会根据所分配对象的预期寿命和大小跟踪不同的区域。例如,最近分配的对象属于年轻一代(Young generation)。当一个对象保持足够长的活动时间时,它可以被提升到老年代(older generation),然后是永久代(permanent generation)。 - Android对每个APP的堆大小设置了硬件限制。
具体的堆大小限制因设备的总体可用RAM大小而异。限制对象可以占用的内存量。每当一个区开始填满时,系统执行垃圾收集事件尝试释放内存。垃圾收集的持续时间取决于它收集的对象的区域以及每个区域中有多少活动对象。如果APP已达到堆容量并试图分配更多内存,则可能会收到OutOfMemoryError。堆的逻辑大小与堆使用的物理内存大小不一样因为有共享内存。确定当前设备上有多少可用堆空间,
可以通过调用getMemoryClass()来查询此方法返回一个整数,指示APP堆可用的兆字节数。
也以通过调用getMemoryInfo()来查询。返回ActivityManager.MemoryInfo对象,该对象提供有关设备当前内存状态的信息,包括可用内存、总内存和内存阈值系统开始终止进程的内存级别。ActivityManager.MemoryInfo对象还公开了一个简单的布尔值lowMemory ,告诉你设备是否内存不足。
public void doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check to see whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if (!memoryInfo.lowMemory) {
// Do memory intensive work ...
}
}
// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}