文件页:代表可回收内存,文件页的大部分可以直接回收,以后有需要时,再从磁盘重新读取;而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放

脏页一般以两个方式写入磁盘:

  • 在应用程序中,通过系统调用 fync,把脏页同步到磁盘中
  • 由内核线程 pdflush 负载这些脏页的刷新

匿名页:应用程序动态分配的堆空间,使用 swap 机制回收

Swap 原理

Swap 简单来说就是把一块磁盘空间或者一个本地文件夹,当成内存来使用。它包括换出和换入两个过程:

  • 换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存
  • 换入,则是把进程再次访问这些内存的时候,把它们从磁盘读到内存中来

常见的笔记本电脑的休眠和快速开机功能,也基于 Swap。休眠时,把系统的内存存入磁盘,这样等到再次开机时,只要从磁盘中加载内存就可以。这样省去了很多应用程序的初始化过程,加快了开机速度。

内存回收的时机:

1、直接内存回收:当有新的大块内存分配请求,但是剩余内存不足,这个时候系统就需要回收一部分内存

2、内核线程 kswapd0来定期回收内存,它定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页最低阈值(pages_low)和页最高阈值(pages_high)。剩余内存,则使用 pages_free 表示

pages_free

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

  • 剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配空间
  • 剩余内存落在页最小阈值和页最低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止
  • 剩余内存落在页最低阈值和页最高阈值中间,说明内存有一定压力,但还可以满足新压力请求
  • 剩余内存大于页内存阈值,说明剩余内存比较多,没有内存压力

页低阈值是由内核选项 /proc/sys/vm/min_free_kbytes 设置,其他两个阈值,都是根据页最小阈值计算生成的

NUMA 和 Swap

在 NUMA 架构下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间。而同一个 Node 内部的内存空间,实际上又可以在进一步分为不同的内存域(Zone),比如直接内存访问区(DMA)、普通内存区(NORMAL)、伪内存区(MOVABLE)等,如下图所示:

Node 内部的内存空间

使用 numactl 命令查看 Node 的分布情况

1
2
3
4
5
6
7
8
9
10
11
12
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29
node 0 size: 130960 MB
node 0 free: 7308 MB
node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39
node 1 size: 131072 MB
node 1 free: 17581 MB
node distances:
node 0 1
0: 10 21
1: 21 10

前面提到的三个内存阈值(页最小阈值、页低阈值和页高阈值),都可以通过内存域在 proc 文件系统中的接口 /proc/zoneinfo 来查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat /proc/zoneinfo| head -n 20
Node 0, zone DMA
pages free 3957
min 7
low 8
high 10
scanned 0
spanned 4095
present 3992
managed 3971
nr_free_pages 3957
nr_alloc_batch 2
nr_inactive_anon 0
nr_active_anon 0
nr_inactive_file 0
nr_active_file 0
nr_unevictable 0
nr_mlock 0
nr_anon_pages 0
nr_mapped 0
nr_file_pages 0

主要指标包括:

  • pages 处的 min、low、high,就是上面提到的三个内存阈值,而 free 是剩余内存页数,它跟后边的 nr_free_pages 相同
  • nr_zone_active_anon 和 nr_zone_inactive_anon,分别代表活跃匿名页不活跃匿名页的数量
  • nr_zone_active_file 和 nr_zone_inactive_file,分别代表活跃文件页不活跃文件页的数量

某个 Node 内存不足时,系统可以从其他 Node 寻找空闲内存,也可以从本地内存种回收内存。具体选哪种模式,你可以通过 /proc/sys/vm/zone_reclaim_mode 来调整。它支持以下几个选项:

  • 默认的是 0,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地内存中回收
  • 1、2、4 都表示只回收本地内存,2 表示可以回写脏数据来回收内存,4 表示可以用 Swap 方式回收内存

swapness

内存回收包括文件页和匿名页:

  • 对文件页的回收,是直接回收缓存,或者把脏页写回磁盘后再回收
  • 对匿名页的回收,是通过 Swap 机制,把它们写入磁盘后再释放内存

Linux 提供了一个 /proc/sys/vm/swapiness 选项,用来调整使用 Swap 的积极程度;swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。

系统 Swap 升高的原因

案例

Linux 本身支持两种类型的 Swap,即 Swap 分区和 Swap 文件,以 Swap 文件为例子,例如如下命令开启 Swap 文件

1
2
3
4
5
6
7
8
# 创建swap文件
fallocate -l 500M /data/swapfile
# 修改权限,仅root用户可读写
chmod 600 /data/swapfile
# 配置swap文件
mkswap /data/swapfile
# 开启swap
swapon /data/swapfile

执行 free 看到 swap 添加成功

1
2
3
4
$ free
total used free shared buff/cache available
Mem: 16092196 562160 613616 295992 14916420 15135272
Swap: 511996 0 511996

执行 dd 命令,模拟大文件的读取

1
dd if=/dev/vdb1 of=/dev/null bs=1G count=400

执行 sar 查看内存和 swap 指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ sar -rS 3
Linux 3.10.107-1-tlinux2_kvm_guest-0051 (VM_194_74_centos) 07/05/20 _x86_64_ (8 CPU)

20:16:14 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
20:16:17 12016228 4075968 25.33 1944876 394804 3089884 18.61 2364636 1364468 88

20:16:14 kbswpfree kbswpused %swpused kbswpcad %swpcad
20:16:17 511996 0 0.00 0 0.00

20:16:17 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
20:16:20 11542068 4550128 28.28 2405684 394868 3089856 18.61 2364984 1825212 120

20:16:17 kbswpfree kbswpused %swpused kbswpcad %swpcad
20:16:20 511996 0 0.00 0 0.00
...

20:16:50 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
20:16:53 6331744 9760452 60.65 7474548 395052 3089736 18.61 2364948 6894124 84

20:16:50 kbswpfree kbswpused %swpused kbswpcad %swpcad
20:16:53 511996 0 0.00 0 0.00
...

20:17:44 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
20:17:47 90140 16002056 99.44 13573540 392440 3089728 18.61 2180308 13149900 164

20:17:44 kbswpfree kbswpused %swpused kbswpcad %swpcad
20:17:47 486836 25160 4.91 120 0.48
...

20:18:44 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
20:18:47 87988 16004208 99.45 13658536 354504 3089760 18.61 2112144 13218932 140

20:18:44 kbswpfree kbswpused %swpused kbswpcad %swpcad
20:18:47 403652 108344 21.16 140 0.13

可以看到,总的内存使用率(%memused)在不断增长,从开始的 25% 一直涨到了 99%,并且主要内存都被缓冲区(kbbuffers)占用,大致的变化过程为:

  • 刚开始,剩余内存(kbmemfree)不断减少,而缓冲区(kbbuffers)则不断增大,由此可知,剩余内存不断分配给了缓冲区
  • 一段时间后,剩余内存已经很小了,而缓存区占用了大部分内存。此时,Swap 的使用开始逐渐增大,缓冲区和剩余内存则只在小范围内波动

为什么 Swap 会升高呢?(按理来说,应该先回收缓冲区的内存,这属于可回收内存),观察 /proc/zoneinfo 指标如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ watch -d grep -A 15 'Normal' /proc/zoneinfo

Every 2.0s: grep -A 15 Normal /proc/zoneinfo Sun Jul 5 20:19:39 2020

Node 0, zone Normal
pages free 5200
min 3268
low 4085
high 4902
scanned 24
spanned 3407872
present 3407872
managed 3276302
nr_free_pages 5200
nr_inactive_anon 134532
nr_active_anon 246943
nr_inactive_file 2466171
nr_active_file 280987
nr_unevictable 0
nr_mlock 0

可以看到,剩余内存(pages free)在一个很小范围内不停地波动。当它小于页低阈值(pages low)时,又会突然增大到一个大于页高阈值(pages high)的数值

  • 当剩余内存小于页低阈值时,系统会回收一些缓存和匿名内存,使剩余内存增大。其中,缓存的回收导致 sar 中的缓冲区减少,而匿名内存的回收导致了 Swap 的使用增大。其中,缓存的回收导致 sar 中的缓冲区减少,而匿名内存的回收导致了 Swap 的使用增大。
  • 同事由于 dd 还在继续,剩余内存又会重新分配给缓存,导致剩余内存减少,缓冲区增大

利用 proc 文件系统,可以查看 Swap 换出的虚拟内存大小,它保存在 /proc/pid/status 中 VmSwap

1
2
3
4
5
6
7
8
9
10
11
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
systemd-journal 3048 86160 kB
writeback 50
watchdog/7 41
watchdog/6 36
watchdog/5 31
watchdog/4 26
watchdog/3 21
watchdog/2 16
watchdog/1 11
watchdog/0 10

可以看到,使用 swap 较多的是 systemd-journal

结束之后,需要关闭 swap

1
swapoff -a

一般关闭 swap 并重新打开,可以这么执行(是一种常见的 swap 清理方法)

1
swapoff -a && swapon /data/swapfile

小结

在内存资源紧张时,Linux 会通过 Swap,把不常访问的匿名页换出到磁盘中,下次访问的时候再从磁盘换入到内存中来。你可以设置 /proc/sys/vm/min_free_kbytes,来调整系统定期回收内存的阈值;也可以设置 /proc/sys/vm/swappiness,来调整文件页和匿名页的回收倾向。

当 Swap 变高时,你可以用 sar、/proc/zoneinfo、/proc/pid/status 等方法,查看系统和进程的内存使用情况,进而找出 Swap 升高的根源和受影响的进程。

通常,降低 Swap 的使用,可以提高系统的整体性能。有几种常见的降低方法:

  • 禁用 Swap,现在服务器的内存走足够大,所有除非有必要,一般会禁用 Swap,大部分云平台中的虚拟机都默认禁用 Swap
  • 如果实在需要用到 Swap,可以尝试降低 swappiness值,减少内存回收时 Swap 的使用倾向
  • 响应延迟敏感的应用,如果它们可能在开启 Swap 的服务器中运行,你还可以使用库函数 mlock() 或者 mlockall() 锁定内存,阻止它们的内存换出

常见的三种清理缓存的方法:

1、清理 pagecache

1
echo 1 > /proc/sys/vm/drop_caches  # 或者 sysctl -w vm.drop_caches = 1

2、清理 dentries 和 inodes

1
echo 2 > /proc/sys/vm/drop_caches  # 或者 sysctl -w vm.drop_caches = 2

3、清理 pagecache、dentries 和 inodes

1
echo 3 > /proc/sys/vm/drop_caches  # 或者 sysctl -w vm.drop_caches = 3

4、使用 sync 命令来清理文件系统内存,还会清理僵尸(zombie)对象和它们占用的内存

1
sync