Linux 的swap 分区

Linux 内存测漏会导致内存紧张,从而触发系统直接回收内存和OOM。

  • OOM 直接杀死进程从而释放内存
  • 内存回收, 大部分文件页都可以直接回收,比如缓存和缓冲区,就属于可回收内存,他们在内存管理中,被叫文件页。

大部分文件页都可以直接被回收,以后有需要在从磁盘读取,而那些暂时还没写入磁盘的数据(脏页),就得先写入磁盘,然后在进行释放。

脏页的写入磁盘方式:

  • 通过系统调用 fsync ,把脏页同步到磁盘中;
  • 也可以交给系统,由内核线程 pdflush 负责这些脏页的刷新。 除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取。

应用程序动态分配的堆内存,也就是匿名页(Anonymous Page)不能被直接回收。但是如果很少被访问,Linux 会将它们暂时存放在磁盘里面,也就是swap中,然后释放内存。

SWAP 原理

Swap 就是把一块磁盘空间或者一个本地文件(以下讲解以磁盘为例),当成内存来使用。它包括换出和换入两个过程。

除此之外,还定义了一个进程 kswapd0 来定期回收内存,kswapd0 定义了三个阈值,分别是

  • 页最小阈值(pages_min)
  • 页低阈值(pages_low)
  • 页高阈值(pages_high)

剩余内存,则使用 pages_free 表示。

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

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

一旦剩余内存小于页低阈值,就会触发内存的回收。这个页低阈值,其实可以通过内核选项 /proc/sys/vm/min_free_kbytes 来间接设置。min_free_kbytes 设置了页最小阈值,而其他两个阈值,都是根据页最小阈值计算生成的,计算方法如下 :

1
2
pages_low = pages_min*5/4
pages_high = pages_min*3/2

##NUMA 和 swap

很多情况下,你明明发现了 Swap 升高,可是在分析系统的内存使用时,却很可能发现,系统剩余内存还多着呢。这有可能是 NUMA 架构导致的。

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

使用 numactl 查看 node 分布

1
2
3
4
5
6
7
8
root@linux:~# numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7965 MB
node 0 free: 7631 MB
node distances:
node 0
0: 10

上面我们发现系统就一个node 也就是node 0, 编号为 0和1,node 0 内存为 7965MB 剩余 7631. 查看阈值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ 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_ fhle 和nr_ zone_ inactive_ fle, 分别是活跃和非活跃的文件页数。

从这个输出结果可以发现,剩余内存远大于页高阈值,所以此时的kswapd0不会回收内存。

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

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

swappiness

到这里,我们就可以理解内存回收的机制了。这些回收的内存既包括了文件页,又包括了匿名页。

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

不过,你可能还有一个问题。既然有两种不同的内存回收机制,那么在实际回收内存时,到底该先回收哪一种呢?
实,Linux 提供了一个/proc/sys/vm/swappiness选项,用来调整使用Swap的积极程度。

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

虽然swappiness的范围是0-100,不过要注意,这并不是内存的百分比,而是调整Swap积极程度的权重,即使你把它设置成0,当剩余内存+文件页小于页高阈值时,还是会发生Swap。

案例

  1. 创建交换分区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@linux:~# free
total used free shared buff/cache available
Mem: 8156288 230724 6775564 7468 1150000 7665632
Swap: 0 0 0
root@linux:~# fallocate -l 8G /mnt/swapfile
root@linux:~# chmod 600 /mnt/swapfile
root@linux:~# mkswap /mnt/swapfile
Setting up swapspace version 1, size = 8 GiB (8589930496 bytes)
no label, UUID=ceee5c74-7eee-4c46-a354-9468a92d7722
root@linux:~# swapon /mnt/swapfile
root@linux:~# free
total used free shared buff/cache available
Mem: 8156288 157116 7762788 6028 236384 7756172
Swap: 8388604 0 8388604
root@linux:~#

执行

1
root@linux:~# dd if=/dev/vdc of=/dev/null bs=1G count=2048

执行 sar -r -S 1

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
# sar -r -S 1  # -r 表示显示内存使用情况,-S 表示显示swap 使用情况
04:48:55 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:48:56 PM 3736788 6692972 4419500 54.19 2967752 196772 1451516 8.77 1188364 3102932 212

04:48:55 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
04:48:56 PM 8388604 0 0.00 0 0.00

04:48:56 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:48:57 PM 3626516 6696040 4529772 55.54 3080392 197252 1434824 8.67 1186304 3215884 384

04:48:56 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
04:48:57 PM 8388604 0 0.00 0 0.00

04:48:57 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:48:58 PM 3514288 6695816 4642000 56.91 3192264 197252 1434824 8.67 1186308 3327760 452

04:48:57 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
04:48:58 PM 8388604 0 0.00 0 0.00

04:48:58 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:48:59 PM 3401440 6695644 4754848 58.30 3304904 197208 1434824 8.67 1186308 3440356 452

04:48:58 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
04:48:59 PM 8388604 0 0.00 0 0.00

04:48:59 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:49:00 PM 3288592 6695628 4867696 59.68 3417552 197252 1434824 8.67 1186364 3553004 56

04:48:59 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
04:49:00 PM 8388604 0 0.00 0 0.00

04:49:00 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:49:01 PM 3175620 6695400 4980668 61.07 3530192 197284 1434824 8.67 1186364 3665668 56

参数说明:

  • kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分比。
  • kbactive, 表示活跃内存,也就是最近使用过的内存,一 般不会被系统回收。
  • kbinact, 表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。

发现总的内存使用率(%memused)在不断增长,从开始的 54% 一直长到了 61%,并且主要内存都被缓冲区(kbbuffers)占用。

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

使用cachetop 观察,发现 dd 读写请求只有50% 命中

1
2
3
4
5
6
7
16:52:14 Buffers MB: 5890 / Cached MB: 181 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
1003 syslog rs:main Q:Reg 4 1 1 60.0% 0.0%
1728 root cachetop 6 0 0 100.0% 0.0%
210 root jbd2/vda1-8 7 5 5 16.7% 16.7%
272 root systemd-journal 38 29 29 13.4% 0.0%
1670 root dd 141063 140816 0 50.0% 50.0%

执行 watch -d grep -A 15 ‘Normal’ /proc/zoneinfo 观察

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Every 2.0s: grep -A 15 Normal /proc/zoneinfo                                                                                                                                                                   linux: Thu Mar 28 16:54:34 2019

Node 0, zone Normal
pages free 17538
min 10560
low 13200
high 15840
spanned 1310720
present 1310720
managed 1269191
protection: (0, 0, 0, 0, 0)
nr_free_pages 17538
nr_zone_inactive_anon 20379
nr_zone_active_anon 262562
nr_zone_inactive_file 918902
nr_zone_active_file 18037
nr_zone_unevictable 0
nr_zone_write_pending 44