Android Lmkd 低内存终止守护程序
一、低内存终止守护程序
Android 低内存终止守护程序 (lmkd) 进程可监控运行中的 Android 系统的内存状态,并通过终止最不必要的进程来应对内存压力大的问题,使系统以可接受的性能水平运行。
所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过socket传递给lmkd。lmdk根据内核的版本情况,或传递给kernel或自身处理低内存回收机制。为腾出更多的内存空间,在内存达到一定阀值时会触发清理oom_adj值高的进程。
1. 内存压力简介
并行运行多个进程的 Android 系统可能会遇到系统内存耗尽,需要更多内存的进程出现明显延迟的情况。内存压力是系统内存不足的一种状态,它需要 Android 通过限制或终止不必要的进程、请求进程释放非关键缓存资源等方式来释放内存(以缓解这种压力)。
过去,Android 使用内核中的低内存终止守护程序 (LMK) 驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,LMK 驱动程序已从上游内核中移除,改由用户空间 lmkd 来执行内存监控和进程终止任务。
2. 压力失速信息
Android 10 及更高版本支持新的 lmkd 模式,它使用内核压力失速信息 (PSI) 监视器来检测内存压力。上游内核中的 PSI 补丁程序集(已向后移植到 4.9 和 4.14 内核)可测量由于内存不足导致任务延迟的时间。由于这些延迟会直接影响用户体验,因此它们代表了确定内存压力严重性的便捷指标。上游内核还包括 PSI 监视器,这类监视器允许特权用户空间进程(例如 lmkd)指定这些延迟的阈值,并在突破阈值时从内核订阅事件。
① PSI 监视器与 vmpressure 信号
由于 vmpressure 信号(由内核生成,用于检测内存压力并由 lmkd 使用)通常包含大量误报,因此 lmkd 必须执行过滤以确定是否真的存在内存压力。这会导致不必要的 lmkd 唤醒并使用额外的计算资源。使用 PSI 监视器可以实现更精确的内存压力检测,并最大限度地减少过滤开销。
②使用 PSI 监视器
如需使用 PSI 监视器(而不是 vmpressure 事件),请配置 ro.lmk.use_psi 属性。默认值为 true,这会以 PSI 监视器作为 lmkd 内存压力检测的默认机制。由于 PSI 监视器需要内核支持,因此内核必须包含 PSI 向后移植补丁程序,并在启用 PSI 支持 (CONFIG_PSI=y) 的情况下进行编译。
3. 内核中 LMK 驱动程序的缺点
由于存在大量问题,Android 弃用了 LMK 驱动程序,问题包括:
- 对于低内存设备,必须主动进行调整,即便如此,在处理涉及支持大文件的活跃页面缓存的工作负载时,其性能也较差。性能不良会导致出现抖动,但不会终止。
- LMK 内核驱动程序依赖于可用内存限制,不会根据内存压力进行扩缩。
- 由于设计的严格性,合作伙伴通常会自定义该驱动程序,使其可以在自己的设备上使用。
- LMK 驱动程序已挂接到 Slab Shrinker API,该 API 并非为了执行繁重操作(例如搜索并终止目标)而设计,这类操作会导致 vmscan 进程变慢。
4. 用户空间 lmkd
用户空间 lmkd 可实现与内核中的驱动程序相同的功能,但它使用现有的内核机制检测和评估内存压力。这些机制包括使用内核生成的 vmpressure 事件或压力失速信息 (PSI) 监视器来获取关于内存压力水平的通知,以及使用内存 cgroup 功能限制根据进程的重要性分配给每个进程的内存资源。
在 Android 10 中使用用户空间 lmkd
在 Android 9 及更高版本中,用户空间 lmkd 会在未检测到内核中的 LMK 驱动程序时激活。由于用户空间 lmkd 要求内核支持内存 cgroup,因此必须使用以下配置设置编译内核:
CONFIG_ANDROID_LOW_MEMORY_KILLER=n
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
终止策略
用户空间 lmkd 支持基于以下各项的终止策略:vmpressure 事件或 PSI 监视器、其严重性以及交换利用率等其他提示。低内存设备和高性能设备的终止策略有所不同:
- 对于内存不足的设备,一般情况下,系统会选择承受较大的内存压力。
- 对于高性能设备,如果出现内存压力,则会视为异常情况,应及时修复,以免影响整体性能。
您可以使用 ro.config.low_ram 属性配置终止策略。如需了解详情,请参阅低 RAM 配置。
用户空间 lmkd 还支持一种旧模式,在该模式下,它使用与内核中的 LMK 驱动程序相同的策略(即可用内存和文件缓存阈值)做出终止决策。要启用旧模式,请将 ro.lmk.use_minfree_levels 属性设置为 true。
5. 图解
5-1. LMK/LMKD
5-2. lmkd kill process flow
lmkd can use minfree table to adjust which adj process to kill (ro.lmk.use_minfree_levels=1)
or use medium/critical pressure to adjust which adj process to kill (ro.lmk.use_minfree_levels=0).
the midum/citical pressure adjust was configured by ro.lmk.medium/ro.lmk.critical , which default value was 800/0 .
lmkd log:(in main log)
ro.lmk.use_minfree_levels=1
ro.lmk.use_minfree_levels=0
5-3. lmk kill process flow
lmk log: (in kernel log)
6 .minfree table & oom adj
6.1 minfree table
adb shell cat /sys/module/lowmemorykiller/parameters/minfree
或者
adb root
adb shell getprop |grep minfree
[sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,36864:900,46080:950]
6.2 如何修改?
•可以通过修改修改minfree表
(frameworks/base/core/res/res/values/config.xml)
config_lowMemoryKillerMinFreeKbytesAbsolute
config_lowMemoryKillerMinFreeKbytesAdjust
(frameworks/base/services/core/java/com/android/server/am/ProcessList.java)
updateOomLevels() function's calculation formula or
default value table(mOomMinFreeLow/mOomMinFreeHigh)
6.3 oom adj
ADJ优先级> | OOMADJ | 对应场景 |
UNKNOWN_ADJ | 1001 | 一般指将要会缓存进程,无法获取确定值 |
CACHED_APP_MAX_ADJ | 906 | 不可见进程的adj最大值(不可见进程可能在任何时候被杀死) |
CACHED_APP_MIN_ADJ | 900 | 不可见进程的adj最小值(不可见进程可能在任何时候被杀死) |
SERVICE_B_ADJ | 800 | B List中的Service(较老的、使用可能性更小) |
PREVIOUS_APP_ADJ | 700 | 上一个App的进程(比如APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ) |
HOME_APP_ADJ | 600 | Home进程 |
SERVICE_ADJ | 500 | 服务进程(Service process) |
HEAVY_WEIGHT_APP_ADJ | 400 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
BACKUP_APP_ADJ | 300 | 备份进程 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知进程,比如后台音乐播放 |
VISIBLE_APP_ADJ | 100 | 可见进程(可见,但是没能获取焦点,比如新进程仅有一个悬浮Activity,Visible process) |
FOREGROUND_APP_ADJ | 0 | 前台进程(正在展示是APP,存在交互界面,Foreground process) |
PERSISTENT_SERVICE_ADJ | -700 | 关联着系统或persistent进程 |
PERSISTENT_PROC_ADJ | -800 | 系统persistent进程,比如telephony |
SYSTEM_ADJ | -900 | 系统进程 |
NATIVE_ADJ | -1000 | native进程(不被系统管理) |
7. lmkd parameters
Parameter | Description | Default | LowRam |
ro.lmk.debug | debug 开关, 除了killing log以外的debug 讯息需要打开这个才能看的到 | false | |
ro.lmk.kill_heaviest_task | 默认false - 每次需要杀进程时,则从高oom_adj开始遍历,同oom_adj时从最后加入列表的开始杀,直到释放出足够内存为止; true - 每次需要杀进程时,则从高oom_adj开始遍历,同oom_adj时从rss最高的进程开始杀(参考节点/proc/<$pid>/statm中的第二个数值),直到释放出足够内存为止; | false | |
ro.config.low_ram | 一般ago device定义为low ram device , 目前是1GB ram 以下的device , 有两个特点 1.依据不同oomadj 限制内存 , 2.一次只会kill 一个process | false | |
ro.lmk.kill_timeout_ms | kill process 后下次kill 中间间隔的 timeout 时间 | 0 | |
ro.lmk.use_minfree_levels | 采用kernel lowmemory killer 的 cache /minfree 参考机制来kill process , 而非参考memory pressure | false | |
Mem Pressure relative | mp_event_common 使用的prop,和PSI 参数不同时生效 | ||
ro.lmk.low | memory pressure 为low 时kill 的最低 adj | 1001 | |
ro.lmk.medium | memory pressure 为medium时kill 的最低 adj | 800 | |
ro.lmk.critical | memory pressure 为high时kill 的最低 adj | 0 | |
ro.lmk.critical_upgrade | 允许 memory pressure 从medium 被上升到critical , 条件是mem_pressure计算低于upgrade_pressure临界值 | false | |
ro.lmk.upgrade_pressure | critical pressure 的参考值 , 以上为medium , 以下为critical | 100 | |
ro.lmk.downgrade_pressure | medium pressure 的参考值 , 以上为low ,以下为medium | 100 | |
PSI relative (>=AndroidQ) | mp_event_psi使用的参数,和Pressuure参数不同时生效 | ||
ro.lmk.use_psi | kernel 使用psi event上发lmkd | 1 | 1 |
ro.lmk.use_new_strategy | 1: use mp_event_psi , 0: use mp_event_common to kill process | 0 | 1 |
ro.lmk.swap_free_low_percentage | 判定swap low的百分比 ex : swap free < 10/100 | 20 | 10 |
ro.lmk.swap_util_max | 最大内存交换量:占可交换内存的百分比。(默认值实际上会停用此功能) | 100 | 100 |
ro.lmk.thrashing_limit | 判定 thrashing 的标准值 | 100 | 30 |
ro.lmk.thrashing_limit_decay | thrashing limit衰减百分比 , 每次衰减 | 10 | 50 |
ro.lmk.psi_partial_stall_ms | 内存失速阈值。用于触发内存不足的通知。 Default for low-RAM devices = 200, for high-end devices = 70 (PSI_SOME) | 70 | 200 |
ro.lmk.psi_complete_stall_ms | 完全PSI失速阈值。用于触发关键内存通知。 Default =700 (PSI_FULL) | 700 | 700 |
ro.lmk.thrashing_min_score_adj | 发生thrashing 时kill 的 min score adj | 200 | 200 |
二、低内存的数据特征和行为特征
1、Meminfo 信息
最简单的方法是使用 Android 系统自带的 Dumpsys meminfo 工具
1 | adb shell dumpsys meminfo |
如果系统处于低内存的话 , 会有如下特征:
- FreeRam 的值非常少 , Used RAM 的值非常大
- ZRAM 使用率非常高(如果开了 Zram 的话)
2、LMK && kswapd 线程活跃
低内存的时候, LKMD 会非常活跃, 在 Kernel Log 里面可以看到 LMK 杀进程的信息:
1 | [kswapd0] lowmemorykiller: Killing 'u.mzsyncservice' (15609) (tgid 15609), adj 906, |
上面这段 Log 的意思是说, 由于 mem 低于我们设定的 900 的水位线 (261272kB),所以把 pid 为 15609 的 mzsyncservice 这个进程杀掉(这个进程的 adj 是 906 )
3、proc/meminfo
这里是 Linux Kernel 展示 meminfo 的地方 , 关于 meminfo 的解读,可以参考这篇文章:/PROC/MEMINFO之谜
从结果来 , 当系统处于低内存的情况时候 , MemFree 和 MemAvailable 的值都很小
shell cat proc/meminfo
1 | MemTotal: 5630104 kB |
4、整机卡顿 && 响应慢
低内存的时候,整机使用的时候要比非低内存的时候要卡很多,点击应用或者启动 App 都会有不顺畅或者响应慢的感觉
三、低内存对性能的具体影响
1、LMK 频繁工作抢占 cpu
LMK 工作时, 会占用 cpu 资源 , 其表现主要有下面几点
- CPU 资源 : 由于 LMK 杀掉的进程通常都是一些 Cache 或者 Service , 这些进程由于低内存被杀之后 , 通常会很快就被其主进程拉起来, 然后又被 LMK 杀掉, 从而进入了一种循环. 由于起进程是一件很消耗 cpu 的操作, 所以如果后台一直有进程被杀和重启, 那么前台的进程很容易出现卡顿
- Memory : 由于低内存的原因, 很容易触发各个进程的 GC , 如下图的 CPU 状态可以看到, 用于内存回收的 HeapTaskDeamon 出现非常频繁
- IO : 低内存会导致磁盘 IO 变多, 如果频繁进行磁盘 IO , 由于磁盘IO 很慢, 那么主线程会有很多进程处于等 IO 的状态, 也就是我们经常看到的 Uninterruptible Sleep
2、影响主线程 IO 操作
主线程出现大量的 IO 相关的问题 ,
- 反馈到 Trace 上就是有大量的黄色 Trace State 出现 , 例如 : Uninterruptible Sleep | WakeKill - Block I/O .
- 查看其 Block 信息 (kernel callsite when blocked:: “wait_on_page_bit_killable+0x78/0x88)
Linux 系统的 page cache 链表中有时会出现一些还没准备好的 page ( 即还没把磁盘中的内容完全地读出来 ) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.
当出现大量的 IO 操作的时候,应用主线程的 Uninterruptible Sleep 也会变多,此时涉及到 io 操作(比如 view ,读文件,读配置文件、读 odex 文件),都会触发 Uninterruptible Sleep , 导致整个操作的时间变长
3、出现 CPU 竞争
低内存会触发 Low Memory Killer 进程频繁进行扫描和杀进程,kswapd0 是一个内核工作线程,内存不足时会被唤醒,做内存回收的工作。 当内存频繁在低水位的时候,kswapd0 会被频繁唤醒,占用 cpu ,造成卡顿和耗电。
比如下面这个情况, kswapd0 占用了 855 的超大核 cpu7 ,而且是满频在跑,耗电可想而知,如果此时前台应用的主线程跑到了 cpu7 上,很大可能会出现 cpu 竞争,导致调度不到而丢帧。
HeapTaskDaemon 通常也会在低内存的时候跑的很高
, 来做内存相关的操作
4、进程频繁查杀和重启
对 AMS 的影响主要集中在进程的查杀上面 , 由于 LMK 的介入 , 处于 Cache 状态的进程很容易被杀掉 , 然后又被他们的父进程或者其他的应用所拉起来 , 导致陷入了一种死循环 . 对系统 CPU \ Memory \ IO 等资源的影响非常大.
比如下面就是一次 Monkey 之后的结果 , QQ 在短时间内频繁被杀和重启 .
14:32:16.932 1435 1510 I am_proc_start: [0,30387,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
1 | 07-23 14:32:16.969 1435 3420 I am_proc_bound: [0,30387,com.tencent.mobileqq] |
其对应的 Systrace - SystemServer 中可以看到 AM 在频繁杀 QQ 和起 QQ
此 Trace 对应的 Kernel 部分也可以看到繁忙的 cpu
5、影响内存分配和触发 IO
手机经过长时间老化使用整机卡顿一下 , 或者整体比刚刚开机的时候操作要慢 , 可能是因为触发了内存回收或者 block io , 而这两者又经常有关联 . 内存回收可能触发了 fast path 回收 \ kswapd 回收 \ direct reclaim 回收 \ LMK杀进程回收等。(fast path 回收不进行回写)
回收的内容是匿名页 swapout 或者 file-backed 页写回和清空。(假设手机都是 swap file 都是内存,不是 disk), 涉及到 file 的,都可能操作 io,增加 block io 的概率。
还有更常见的是打开之前打开过的应用,没有第一次打开的快,需要加载或者卡一段时间 . 可能发生了 do_page_fault,这条路径经常见到 block io 在 wait_on_page_bit_killable(),如果是 swapout 内存,就要 swapin 了。如果是普通文件,就要 read out in pagecache/disk.
do_page_fault —> lock_page_or_retry -> wait_on_page_bit_killable 里面会判断 page 是否置位 PG_locked, 如果置位就一直阻塞, 直到 PG_locked 被清除 , 而 PG_locked 标志位是在回写开始时和 I/O 读完成时才会被清除,而 readahead 到 pagecache 功能也对 block io 产生影响,太大了增加阻塞概率。
四、实例
下面这个 Trace 是低内存情况下 , 抓取的一个 App 的冷启动 , 我们只取应用启动到第一帧显示的部分 ,总耗时为2s 。
可以看到其 Running 的总时间是 682 ms ,
1、低内存的启动情况
低内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 共花费了 2s . 从下面的 Thread 信息那里可以看到
- Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 这两栏总共花费 750 ms 左右(对比下面正常情况才 130 ms)
- Running 的时间在 600 ms (对比下面正常情况才 624 ms , 相差不大)
从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDaemon 跑的比较多之外 , 其他的内存和 io 相关的进程也非常多 , 比如若干个 kworker 和 kswapd0.
2、正常内存情况下
正常内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 只需要 1.22s . 从下面的 Thread 信息那里可以看到
- Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 这两栏总共才 130 ms.
- Running 的时间是 624 ms
从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDeamon 跑的比较多之外 , 其他的内存和 io 相关的进程非常少.
五、Low memory处理建议
1. 优化系统进程内存占用
参考
Quick Start > Advanced System Debug & Tuning Method > Memory > memory usage分析
排查内存占比高进程并优化
2. 减少reserved memory
2-1 获取reserved memory 讯息:
<=Android P , 参考FAQ21499reserve_memory 用量
>=Android Q, 请提e-service 申请 “memory-layout-parser” 工具
也可从lk log 搜mblock_reserve-R (但可能有缺漏)
Line 1920: [1604] mblock_reserve-R[3].start: 0x46000000, sz: 0x400000 map:0 name:lk_addr_mb
Line 1921: [1605] mblock_reserve-R[4].start: 0x46900000, sz: 0x8000000 map:0 name:scratch_addr_mb
Line 1922: [1606] mblock_reserve-R[5].start: 0x44000000, sz: 0x80000 map:1 name:dtb_kernel_addr_mb
Line 1923: [1607] mblock_reserve-R[6].start: 0x40008000, sz: 0x3200000 map:0 name:kernel_addr_mb
Line 1924: [1608] mblock_reserve-R[7].start: 0x45000000, sz: 0x1000000 map:0 name:ramdisk_addr_mb
Line 1925: [1609] mblock_reserve-R[8].start: 0x77370000, sz: 0xc90000 map:0 name:framebuffer
Line 1926: [1610] mblock_reserve-R[9].start: 0x7fa00000, sz: 0x400000 map:0 name:logo_db_addr_pa
Line 1927: [1611] mblock_reserve-R[10].start: 0x77360000, sz: 0x10000 map:0 name:SPM-reserved
Line 1928: [1612] mblock_reserve-R[11].start: 0x77350000, sz: 0x10000 map:0 name:MCUPM-reserved
Line 1929: [1613] mblock_reserve-R[12].start: 0x72000000, sz: 0x4000000 map:0 name:ccci
或是lk 代码搜
mblock_reserve 或 mblock_reserve_ext
ex:
230 logo_db_addr_pa = (void *)(u32)mblock_reserve_ext(&g_boot_arg->mblock_info, 231 LK_LOGO_MAX_SIZE, PAGE_SIZE, 0x80000000, 0, "logo_db_addr_pa"); |
或.dts 搜 reserved-memory
ex:
318 reserve-memory-scp_share { 319 compatible = "mediatek,reserve-memory-scp_share"; 320 no-map; 321 size = <0 0x01400000>; /*20 MB share mem size */ 322 alignment = <0 0x1000000>; 323 alloc-ranges = <0 0x40000000 0 0x50000000>; /*0x4000_0000~0x8FFF_FFFF*/ 324 }; 325 consys-reserve-memory { 326 compatible = "mediatek,consys-reserve-memory"; 327 no-map; 328 size = <0 0x200000>; 329 alignment = <0 0x200000>; 330 alloc-ranges = <0 0x40000000 0 0x80000000>;
3. 限制后台
3-1修改DEFAULT_MAX_CACHED_PROCESSES
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerConstants.java or ProcessList.java
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; // 改为DEFAULT_MAX_CACHED_PROCESSES = 8 or 16 or ...
3-2修改mCachedRestoreLevel
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java中
long getCachedRestoreThresholdKb() {
return mCachedRestoreLevel; //将mCachedRestoreLevel 改为 mCachedRestoreLevel/2
}
4. 调整lmk参数
4-1. 调整minfree table
<=kernel-4.9 non-ago project or kernel-4.14 (ro.lmk.use_minfree_levels=1)
minfree table后三项阀值 ,分别增大1.x倍 1.x倍,1.x倍 (ex: 1.2 , 1.5 ,...倍)
minfree table 参考
Quick Start > Advanced System Debug & Tuning Method > Memory > lmkd & lmk
4-2. 调整lmkd 参数
Ago project , or kernel-4.14 (ro.lmk.use_minfree_levels=0)
ro.lmk.medium 调小(mediaum pressure kill adj 减小, 更多进程可杀)
ro.lmk.downgrade_pressure 调大(更容易进到mediaum pressure状态)
ro.lmk.upgrade_pressure 调大(更容易进到critical pressure状态)
5. swap szie & swappiness
5-1.调大swap size, 使系统逻辑内存延伸加大
/device/mediatek/mt6xxx/
/device/mediatek/vendor/common/
fstab.enableswap
fstab.enableswap_gmo
fstab.enableswap_ago
/dev/block/zram0 none swap defaults zramsize=xx% 把值或百分比调大
可从/proc/zraminfo确认是否生效
5-2.调大swappiness, 使系统充分利用swap 分区
/proc/sys/vm/swappiness
/dev/memcg/memory.swappiness
/dev/memcg/apps/memory.swappiness
/dev/memcg/system/memory.swappiness
6. Duraspeed enable (or 做好后台管理)
duraspeed 可主动管理后台进程与内存, 避免进入内存恶劣情况
7. 其他优化方案
- 提高 extra_free_kbytes 值
- 提高 disk I/O 读写速率,如用 UFS3.0,用固态硬盘
- 避免设置太大的 read_ahead_kb 值
- 使用 cgroup 的 blkio 来限制后台进程的 io 读操作,缩短前台 io 响应时间
- 提前做内存回收的操作,避免在用户使用应用时碰到而感受到稍微卡顿
- 增加 LMK 效率,避免无效的 kill
- kswapd 周期性回收更多的 high 水位
- 调整 swappiness 来平衡 pagecache 和 swap
- 策略 : 针对低内存机器做特殊的策略 , 比如杀进程更加激进 (这会带来用户体验的降低 , 所以这个度需要兼顾性能和用户体验)
- 策略 : 在内存不足的时候提醒用户(或者不提醒用户) , 杀掉不必要的后台进程 .
- 策略 : 在内存严重不足且无法恢复的情况下 , 可以提示用户重启手机.
八.Slab内存占用以致Kill应用程序问题分析
一般的,都是有应用程序向系统申请内存,但是系统发现剩余的内存大小无法满足当前的申请,进行一系列的操作之后还是无法满足,将会选择最合适的程序将其kill,这样系统将可以回收它的内存,从而满足系统中其他进程的内存需求。所以,程序被kill掉,并不一定说该程序有内存泄露,只是说当系统内存被kill时,它最适合被kill。
在程序被kill之前,可以查看进程占用的内存信息,看看进程是否存在内存泄露:
其中部分信息如下: VmPeak: 3068 kB VmSize: 3068 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 612 kB VmRSS: 612 kB
我们主要查看VmRSS的大小是否逐渐在增大,如果该值逐渐增大,很大可能是程序存在内存泄露。但是在test测试中,程序的该值并没有很明显的变化,所以转向系统内存信息。
每隔一段时间,查看系统内存的信息,操作如下:
root@Linux: /# cat /proc/meminfo MemTotal: 493184 kB MemFree: 442572 kB MemAvailable: 452300 kB Buffers: 3424 kB Cached: 3224 kB SwapCached: 0 kB Active: 8940 kB Inactive: 284 kB Active(anon): 2588 kB Inactive(anon): 120 kB Active(file): 6352 kB Inactive(file): 164 kB Unevictable: 0 kB Mlocked: 0 kB HighTotal: 0 kB HighFree: 0 kB LowTotal: 493184 kB LowFree: 442572 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 20 kB Writeback: 0 kB AnonPages: 2616 kB Mapped: 2204 kB Shmem: 124 kB Slab: 30528 kB SReclaimable: 13904 kB SUnreclaim: 16624 kB KernelStack: 704 kB PageTables: 296 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 246592 kB Committed_AS: 55424 kB VmallocTotal: 507904 kB VmallocUsed: 0 kB VmallocChunk: 0 kB CmaTotal: 65536 kB CmaFree: 59280 kB
通过cat /proc/meminfo查看系统的内存信息,其中,Slab是slab占用的内存大小,SReclaimable是可回收的,而SUnreclaim是不可回收的。发现Slab占用了系统快30M的内存,留意这个信息。接着,再查看一下,slab的详细使用情况:
root@Linux: /# cat /proc/slabinfo slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> ext4_groupinfo_4k 58 81 296 27 2 : tunables 0 0 0 : slabdata 3 3 0 ext4_groupinfo_1k 1 28 288 28 2 : tunables 0 0 0 : slabdata 1 1 0 jbd2_1k 0 0 3072 10 8 : tunables 0 0 0 : slabdata 0 0 0 bridge_fdb_cache 0 0 320 25 2 : tunables 0 0 0 : slabdata 0 0 0 sd_ext_cdb 2 18 216 18 1 : tunables 0 0 0 : slabdata 1 1 0 sgpool-128 2 14 2304 14 8 : tunables 0 0 0 : slabdata 1 1 0 sgpool-64 2 25 1280 25 8 : tunables 0 0 0 : slabdata 1 1 0 sgpool-32 2 21 768 21 4 : tunables 0 0 0 : slabdata 1 1 0 sgpool-16 2 16 512 16 2 : tunables 0 0 0 : slabdata 1 1 0 sgpool-8 2 21 384 21 2 : tunables 0 0 0 : slabdata 1 1 0 cfq_io_cq 10 31 264 31 2 : tunables 0 0 0 : slabdata 1 1 0 cfq_queue 9 22 360 22 2 : tunables 0 0 0 : slabdata 1 1 0 fat_inode_cache 3 26 616 26 4 : tunables 0 0 0 : slabdata 1 1 0 fat_cache 0 0 200 20 1 : tunables 0 0 0 : slabdata 0 0 0 squashfs_inode_cache 88 200 640 25 4 : tunables 0 0 0 : slabdata 8 8 0 jbd2_transaction_s 0 42 384 21 2 : tunables 0 0 0 : slabdata 2 2 0 jbd2_inode 1 76 208 19 1 : tunables 0 0 0 : slabdata 4 4 0...... kmalloc-128 1589 1596 384 21 2 : tunables 0 0 0 : slabdata 76 76 0 kmalloc-64 15937 16200 320 25 2 : tunables 0 0 0 : slabdata 648 648 0 kmem_cache_node 107 125 320 25 2 : tunables 0 0 0 : slabdata 5 5 0 kmem_cache 107 126 384 21 2 : tunables 0 0 0 : slabdata 6 6 0
从这里可以了解到slab的使用情况,记录下来。
slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。
接着将可以隔较长一段时间,重复的进行cat /proc/meminfo和cat /proc/slabinfo操作,对比几次的信息,检查问题。
最后发现,经过较长一段时间的测试之后,Slab占用的内存数量大大增加,如果是slab占用较大的内存,则是内核频繁分配结构体导致,导致系统可用内存减小。直到出现Out of memory导致kill程序。
1.解决
了解到是Slab导致的占用内存过高的问题之后,可以手动的刷Slab,操作如下:
echo 3 > /proc/sys/vm/drop_caches /* 回刷缓冲 */
其中drop_caches的4个值有如下含义:
- 0:不做任何处理,由系统自己管理
- 1:清空pagecache
- 2:清空dentries和inodes
- 3:清空pagecache、dentries和inodes
但是这样的办法不是最佳的,最好还是应该通过slabinfo信息,了解到应用程序进行什么操作,导致内核频繁申请结构体导致Slab占用大量内存,看能否避免这样的问题,同时,内核有自动回收机制,可修改触发自动回收的阀值,当slab空闲内存达到一定量的时候,进行有效的回收。
2.后续
后来在参考文章看到信息,概括如下:
文中开头的说到的老化测试程序test,就是大量的保存文件,频繁的文件io操作(open、write、close),导致了dentry_cache占用了系统太多的内存资源。
inode对应于物理磁盘上的具体对象,而dentry是一个内存实体,其中的d_inode成员指向对应的inode,故可以把dentry看成是Linux文件系统中某个索引节点(inode)的链接,这个索引节点可以是文件,也可以是目录。而dentry_cache是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的,它记录了目录项到inode的映射关系。
3.系统的自动slab缓存回收
在slab缓存中,对象分为SReclaimable(可回收)和SUnreclaim(不可回收),而在系统中绝大多数对象都是可回收的。内核有一个参数,当系统内存使用到一定量的时候,会自动触动回收操作。
- 内核参数:
vm.min_free_kbytes = 836787
代表系统所保留空闲内存的最低限。
在系统初始化时会根据内存大小计算一个默认值,计算规则是:
min_free_kbytes = sqrt(lowmem_kbytes * 16) = 4 * sqrt(lowmem_kbytes)(注:lowmem_kbytes即可认为是系统内存大小)
另外,计算出来的值有最小最大限制,最小为128K,最大为64M。
可以看出,min_free_kbytes随着系统内存的增大不是线性增长,因为随着内存的增大,没有必要也线性的预留出过多的内存,能保证紧急时刻的使用量便足矣。 - min_free_kbytes的主要用途是计算影响内存回收的三个参数 watermark[min/low/high]
- watermark[high] > watermark [low] > watermark[min],各个zone各一套
- 在系统空闲内存低于 watermark[low]时,开始启动内核线程kswapd进行内存回收(每个zone一个),直到该zone的空闲内存数量达到watermark[high]后停止回收。如果上层申请内存的速度太快,导致空闲内存降至watermark[min]后,内核就会进行direct reclaim(直接回收),即直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟,而且可能会触发系统OOM。这是因为watermark[min]以下的内存属于系统的自留内存,用以满足特殊使用,所以不会给用户态的普通申请来用。
- 三个watermark的计算方法:
watermark[min] = min_free_kbytes换算为page单位即可,假设为min_free_pages。(因为是每个zone各有一套watermark参数,实际计算效果是根据各个zone大小所占内存总大小的比例,而算出来的per zone min_free_pages)
watermark[low] = watermark[min] * 5 / 4
watermark[high] = watermark[min] * 3 / 2
所以中间的buffer量为 high - low = low - min = per_zone_min_free_pages * 1/4。因为min_free_kbytes = 4* sqrt(lowmem_kbytes),也可以看出中间的buffer量也是跟内存的增长速度成开方关系。 - 可以通过/proc/zoneinfo查看每个zone的watermark
- min_free_kbytes大小的影响
min_free_kbytes设的越大,watermark的线越高,同时三个线之间的buffer量也相应会增加。这意味着会较早的启动kswapd进行回收,且会回收上来较多的内存(直至watermark[high]才会停止),这会使得系统预留过多的空闲内存,从而在一定程度上降低了应用程序可使用的内存量。极端情况下设置min_free_kbytes接近内存大小时,留给应用程序的内存就会太少而可能会频繁地导致OOM的发生。
min_free_kbytes设的过小,则会导致系统预留内存过小。kswapd回收的过程中也会有少量的内存分配行为(会设上PF_MEMALLOC)标志,这个标志会允许kswapd使用预留内存;另外一种情况是被OOM选中杀死的进程在退出过程中,如果需要申请内存也可以使用预留部分。这两种情况下让他们使用预留内存可以避免系统进入deadlock状态。
可测试,当调整完min_free_kbytes值大于系统空闲内存后,kswapd进程的确从休眠状态进入运行态,开始回收内存。
同时,还有一个参数vm.vfs_cache_pressure = 200
该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低于100,将导致内核倾向于保留directory和inode cache;增加该值超过100,将导致内核倾向于回收directory和inode cache。
相关文章:
Android Lmkd 低内存终止守护程序
一、低内存终止守护程序 Android 低内存终止守护程序 (lmkd) 进程可监控运行中的 Android 系统的内存状态,并通过终止最不必要的进程来应对内存压力大的问题,使系统以可接受的性能水平运行。 所有应用进程都是从zygote孵化出来的,记录在AMS…...
快速掌握 Flutter 图片开发核心技能
大家好,我是 17。 在 Flutter 中使用图片是最基础能力之一。17 做了精心准备,满满的都是干货!本文介绍如何在 Flutter 中使用图片,尽量详细,示例完整,包会! 使用网络图片 使用网络图片超级简…...
复习使用git(二)
删除远程分支 git push origin --delete 分支名 撤销修改 撤销工作区的修改 已修改,但尚未添加(add),使用 git restore 文件名 撤销工作区的修改。 Note: “git checkout – 文件名”,checkout 检出的意思&#x…...
魔兽世界335服务端架设对外网开放的步骤
警告:在没有网络安全防护措施或基础知识的情况下,开放端口可能造成被黑客入侵、流量攻击、破坏数据、资料泄露等情况的发生。在你选择开放端口时,视为已经充分了解可能发生的后果、危害,清楚自己在做什么,并且自己将对…...
华为OD机试模拟题 用 C++ 实现 - 通信误码(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明通信误码题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,...
Vue 核心
文章目录Vue 核心一,Vue 简介(一)官网(二)介绍与描述(三)Vue 的特点(四)与其它 JS 框架的关联(五)Vue 周边库二,初识 Vue三࿰…...
Kylin V10桌面版arm3568 源码安装redis
上传redis-5.0.14.tar.gz到/home/kylin/下载;解压kylinkylin:~/下载$ tar -zxvf redis-5.0.14.tar.gz/opt下新建redis目录,并将上面解压的文件夹移到此处kylinkylin:~/下载$ sudo mv redis-5.0.14 /opt/redis/编译:kylinkylin:/opt/redis/red…...
【ICCV2022】 CAPAO:一种高效的单阶段人体姿态估计模型
CAPAO:一种高效的单阶段人体姿态估计模型 重新思考关键点表示:将关键点和姿态建模作为多人姿态估计的对象(Rethinking Keypoint Representations: Modeling Keypoints and Poses as Objects for Multi-Person Human Pose Estimation…...
ROS1学习笔记:ROS中的坐标管理系统(ubuntu20.04)
参考B站古月居ROS入门21讲:ROS中的坐标系管理系统 基于VMware Ubuntu 20.04 Noetic版本的环境 文章目录一、机器人中的坐标变换二、TF功能包三、小海龟跟随实验3.1 启动实验3.2 查看当前的TF树3.3 坐标相对位置可视化3.3.1 tf_echo3.3.2 rviz一、机器人中的坐标变换…...
requests---(2)session简介与自动写博客
目录:导读 session简介 session登录 自动写博客 获取登录cookies 抓取写博客接口 requests自动写博客 写在最后 http协议是无状态的,也就是每个请求都是独立的。那么登录后的一系列动作,都需要用cookie来验证身份是否是登录状态&#…...
基于 HAProxy + Keepalived 搭建 RabbitMQ 高可用集群
RabbitMQ 集群 通常情况下,在集群中我们把每一个服务称之为一个节点,在 RabbitMQ 集群中,节点类型可以分为两种: 内存节点:元数据存放于内存中。为了重启后能同步数据,内存节点会将磁盘节点的地址存放于磁…...
基于51单片机和proteus的智能调速风扇设计
此智能风扇是基于51单片机和proteus的仿真设计,功能如下: 1. Timer0 PWM控制电机转速 2. DHT11采集温湿度 3. LCD1602显示温湿度及电机状态 4. 按键控制电机加减速启停等 5. 串口控制电机加减速启停等 功能框图如下: Proteus仿真界面如下…...
SQL Server开启CDC的完整操作过程
这里写自定义目录标题写在前面SQL Server开启CDC1. 将指定库的实例先开启CDC2. 开启需要开启CDC的表3. 关闭CDC功能更详细信息参照官网写在前面 鉴于老旧数据的结构和项目都在sqlserver上存储,且迁移成本巨大,当下要为sqlserver的存储过程减负。要将一部…...
【Spring Cloud Alibaba】008-Sentinel
【Spring Cloud Alibaba】008-Sentinel 文章目录【Spring Cloud Alibaba】008-Sentinel一、服务雪崩1、概述2、解决方案常见的容错机制二、Sentinel:分布式系统的流量防卫兵1、**Sentinel** 概述简介特性Sentinel 的开源生态Sentinel 的历史2、Sentinel 基本概念资源…...
解读CRC校验计算
个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 参考:http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html 参考:https://en.wikipedia.org/wiki/Cyclic_redundancy_check 参考:https://www.cnblogs.com/…...
深入理解Spring MVC下
上一篇博客从理论概念上来梳理Spring MVC相关知识,此篇博客将通过spring官网提供showcase代码为例子,详细介绍showcase代码中包含的各个例子是如何实现的。官网的showcase代码包含的主要例子包括,Demo地址:Mapping Requests&#…...
【Linux】ssh-keygen不需要回车,自动生成密钥,批量免密操作!
使用命令ssh-keygen 需要手动敲击回车,才会生成密钥,如下代码所示 [rootlocalhost ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase):…...
C/C++开发,无可避免的内存管理(篇四)-智能指针备选
一、智能指针 采用C/C开发堆内存管理无论是底层开发还是上层应用,无论是开发新手,还是多年的老手,都会不自觉中招,尤其是那些不是自己一手经历的代码,要追溯问题出在哪里更是个麻烦事。C/C程序常常会遇到程序突然退出&…...
VMware ESXi给虚拟机扩容
用ESXi管理的虚拟机硬盘空间不够了,讲一下如何进行扩容。 一、查看现状 通过如下三个命令,可以查看硬盘情况,可以看到只有500G,已经用了45%。这次我们再扩容500G。 df -Th lsblk fdisk -lIDE磁盘的文件名为 /de…...
认识STM32和如何构建STM32工程
STM32介绍什么是单片机单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种/0口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电…...
RabbitMQ延迟队列
目录 一、概念 二、使用场景 三、RabbitMQ 中的 TTL (一)队列设置 TTL (二)消息设置 TTL (三)两者的区别 四、整合SpringBoot实现延迟队列 (一)创建项目 (二&am…...
Java中常用的七种队列你了解多少?
文章目录Java中常用的七种队列你了解多少?ArrayBlockingQueue队列如何使用?添加元素到队列获取队列中的元素遍历队列LinkedBlockingQueue队列如何使用?1. 创建SynchronousQueue对象2. 添加元素到队列3. 获取队列中的元素4. 遍历队列SynchronousQueue队列…...
<Java获取时间日期工具类>常见八种场景(一)
一:自定义时间日期工具类常用的八种方式(整理): 0,getTimeSecondNum:时间日期转成秒数,常用于大小比较 1,getLastYearMonthLastDay:获取去年当月最后一天的时间日期 2,getLastYearM…...
接上一篇 对多个模型环形旋转进行优化 指定旋转位置
using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; public class ModelAnimal : MonoBehaviour { //记录鼠标滑动 public Vector2 lastPos;//鼠标上次位置 Vector2 currPos;//鼠标当前位置 Vector2 offset;//两次位置的偏移…...
Unity中获取地形的法线
序之前,生成了地形图:(42条消息) 从灰度图到地形图_averagePerson的博客-CSDN博客那末,地形的法线贴图怎么获取?大概分为两个部分吧,先拿到法线数据,再画到纹理中去。关于法线计算Unity - Scripting API: M…...
模型解释性:PFI、PDP、ICE等包的用法
本篇主要介绍几种其他较常用的模型解释性方法。 1. Permutation Feature Importance(PFI) 1.1 算法原理 置换特征重要性(Permutation Feature Importance)的概念很简单,其衡量特征重要性的方法如下:计算特征改变后模型预测误差的增加。如果打乱该特征的…...
spring常见面试题(2023最新)
目录前言1.spring是什么2.spring的设计核心是什么3.IOC和AOP面试题4.spring的优点和缺点5.spring中bean的作用域6.spring中bean的注入方式7.BeanFactory 和 ApplicationContext有什么区别?8.循环依赖的情况,怎么解决?9.spring中单例Bean是线程…...
华为OD机试题,用 Java 解【压缩报文还原】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
机器学习-BM-FKNCN、BM-FKNN等分类器对比实验
目录 一、简介和环境准备 二、算法简介 2.1四种方法类: 2.1.1FKNN 2.1.2FKNCN 2.1.3BM-FKNN 2.1.3BM-FKNCN 2.2数据预处理 2.3输出视图 2.4调用各种方法看准确率 2.4.1BM-FKNCN 2.4.2BM-FKNN 2.4.3FKNCN 2.4.4FKNN 2.4.5KNN 一、简介和环境准备 k…...
ChatGPT火了,对话式人工智能还能干嘛?
身兼数职的ChatGPT 从2022火到了2023 连日来一直是各大平台的热议对象 其实除了写诗、敲代码、处理文档 以ChatGPT为代表的 对话式人工智能 还有更重要的工作要做 对话式AI与聊天机器人 相信大多数人…...
网站后台培训方案/百度广告代理商
一、什么是CDN? 内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。CDN应用广泛,支持多种行业、多种场景内容加速ÿ…...
厦门疫情最新消息/seo培训
#!/bin/bash#####################################################Environment Setting####################################################### #程序代码数组APPS(shop euaker config) #程序名称数组 NAMES(店铺模块 euaker模块 config模块) #jar包JARS(message-pushmsg-…...
做网站建设需要什么工具/优化神马网站关键词排名价格
组合模式(Composite pattern) 组合模式看起来就像对象组的树形结构,一个对象里面包含一个或一组其他的对象。它是属于结构型模式。 例如,一个公司包括很多个部门,每个部门又包括很多人,这个用数据结构来表示…...
建立https网站/推广策划方案怎么写
一、 查询要求 Q18语句查询获得比指定供货量大的供货商信息。可用于决定在订单量大,任务紧急时,验证是否有充足的供货商。 Q18语句的特点是:带有分组、排序、聚集、IN子查询操作并存的三表连接操作。查询语句没有从语法上限制返回多少条…...
做网站公司cnfg/网络营销模式案例
用户名 性别 邮件{% for user in users %}{{user.name}}{% if user.sex 1 %}男{% else %}女{% endif %}{{user[email]}}{% else %}暂无数据{% endfor %}...
网站做权重的方法/游戏推广代理app
布局文件的意义 Android中主要用来定义UI界面的一种方式是利用xml布局文件 布局文件必须放到res/layout目录下 ViewGroup.LayoutParams提供两个XML属性设定组件的大小。 android:layout_height:指定该子组件的基本高度; android:layout_width&#x…...