Linux内存回收机制

当Linux系统发现内存紧张时,系统就会通过一系列机制来回收内存,比如下面这三种方式:

回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;

回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;

杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。

回收缓存

回收缓存,也就是回收cache和buffer。缓存的回收有两个触发时机:

当新进程申请内存,系统内存不足时,Linux系统会尝试按照LRU算法回收缓存

后台进程kswapd0会在特定条件下回收缓存

kswapd0在什么条件下会回收缓存?

为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)。剩余内存,则使用 pages_free 表示。

kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。

剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。

剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。

剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。

剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。

可以通过/proc/sys/vm/min_free_kbytes设置pages_min指标,pages_low和pages_high则由pages_min计算而来:

pages_low = pages_min*5/4

pages_high = pages_min*3/2

除了Buffer Cache之外,还有哪些内存可以被回收?

内存映射获取的文件映射页--可以直接回收

应用程序动态分配的堆内存(也就是匿名页)--通过swap方式交换到硬盘--这里引入swap分区

Swap分区

Linux可以通过将内存中的swap分区交换到硬盘上,从而减轻内存压力。

在 NUMA 架构下,每个 Node 都有自己的本地内存空间,而当本地内存不足时,默认既可以从其他 Node 寻找空闲内存,也可以从本地内存回收。查看cpu numa架构信息:

numactl --hardware

从/proc/zoneinfo中查看水位指标:

$ cat /proc/zoneinfo

...

Node 0, zone Normal

pages free 227894

min 14896

low 18620

high 22344

...

nr_free_pages 227894

nr_zone_inactive_anon 11082

nr_zone_active_anon 14024

nr_zone_inactive_file 539024

nr_zone_active_file 923986

...

pages 处的 min、low、high,就是上面提到的三个内存阈值,而 free 是剩余内存页数,它跟后面的 nr_free_pages 相同。

nr_zone_active_anon 和 nr_zone_inactive_anon,分别是活跃和非活跃的匿名页数。

nr_zone_active_file 和 nr_zone_inactive_file,分别是活跃和非活跃的文件页数。

swap分区选项

Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。

swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。

找出使用swap分区最多的进程

for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head

查看swap分区的使用情况

# 间隔 1 秒输出一组数据

# -r 表示显示内存使用情况,-S 表示显示 Swap 使用情况

$ sar -r -S 1

04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty

04:39:57 6249676 6839824 1919632 23.50 740512 67316 1691736 10.22 815156 841868 4

04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad

04:39:57 8388604 0 0.00 0 0.00

sar 的输出结果是两个表格,第一个表格表示内存的使用情况,第二个表格表示 Swap 的使用情况。其中,各个指标名称前面的 kb 前缀,表示这些指标的单位是 KB。

kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分比。

kbactive,表示活跃内存,也就是最近使用过的内存,一般不会被系统回收。

kbinact,表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。

关闭swap分区

在/etc/sysctl.conf中加入

vm.swappiness=0再使用sysctl -p 使配置生效

杀死进程

Linux系统会监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:

一个进程消耗的内存越大,oom_score 就越大;

一个进程运行占用的 CPU 越多,oom_score 就越小。

进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。

把 sshd 进程的 oom_adj 调小为 -16

echo -16 > /proc/$(pidof sshd)/oom_adj