这篇文章上次修改于 761 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

系统内存不足时,可能会进程内存回收。

  • 对于文件页的回收,包括缓存,缓冲区和通过内存映射获取的文件映射页,大部分可以直接清空。如果被应用程序修改过,且暂时还没写入磁盘的数据(脏页),需要先写入磁盘,然后再进行内存释放。

    一般脏页写入磁盘有两种方式:
    系统内存不足时,可能会进程内存回收。

    对于文件页的回收,包括缓存,缓冲区和通过内存映射获取的文件映射页,大部分可以直接清空。如果被应用程序修改过,且暂时还没写入磁盘的数据(脏页),需要先写入磁盘,然后再进行内存释放。

    一般脏页写入磁盘有两种方式:

      在应用程序中通过系统调用 fsync 将脏页写入磁盘。
      交给系统,由内核线程 pdflush 负责脏页的刷新。
    

    对于匿名页的回收,即应用程序动态分配的堆内存,可通过 Swap 机制写到磁盘,然后释放那些内存,再次访问这些内存时,再次从磁盘读入即可。

1 Swap 原理

Swap 将一块磁盘空间或本地文件当作内存使用。

换出:将进程暂时不用的内存数据存储到磁盘,并释放这些数据占用的内存。

换入:在进程再次访问这些内存时,把它们从磁盘读入内存。

Linux 在什么时候需要回收内存:

有新的大块内存申请,剩余内存不足时,需要回收一部分内存,称为直接内存回收。
有一个专门的内核线程 kswapd0 定期回收内存。kswapd0 定义了三个阈值,这三个阈值可在 /proc/zoneinfo 中调整。当剩余内存位于 pages_min 和 pages_low 之间时,kswapd0 会进行内存回收,直到剩余内存大与 pages_hig。![img](https://aping-blog-1257909822.cos.ap-beijing.myqcloud.com/19.png)img

2 NUMA 与 Swap

NUMA 架构下,多个处理器被分配到不同的 Node 上,且每个 Node 都有自己的本地内存空间。

查看 NUMA:
​shell

$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

显示系统中只有一个 Node,即 Node 0,CPU 0 和 CPU 1 都在该 Node 上。Node 0 内存大小为 7977MB,剩余内存大小为 4416MB。

当某个 Node 的内存不足时,系统可以从其它 Node 寻找空闲内存,也可以从本地回收空闲内存,可通过 /proc/sys/vm/zone_reclaim_mode 调整。

默认是 0,表示既可以从其它 Node 寻找空闲内存,也可以从本地回收空闲内存。
1,2,4 表示只可回收本地空闲内存,2 表示通过回写脏数据回收内存,4 表示通过 Swap 机制回收内存。

3 swappiness

Linux 提供了 /proc/sys/vm/swappiness 选项来调整 Swap 积极程度。swappiness 范围为 [0, 100],数值越大,越积极使用 Swap;反之,更倾向于回收文件页。

4 开启 Swap

​shell

$ free

         total        used        free      shared  buff/cache   available

Mem: 8169348 331668 6715972 696 1121708 7522896
Swap: 0 0 0

$ free

         total        used        free      shared  buff/cache   available

Mem: 8169348 331668 6715972 696 1121708 7522896
Swap: 0 0 0

可以看出,Swap 的 total 为 0,说明没有设置 Swap。

设置并开启 Swap:
​shell

创建Swap文件

$ fallocate -l 8G /mnt/swapfile

修改权限只有根用户可以访问

$ chmod 600 /mnt/swapfile

配置Swap文件

$ mkswap /mnt/swapfile

开启Swap

$ swapon /mnt/swapfile

创建Swap文件

$ fallocate -l 8G /mnt/swapfile

修改权限只有根用户可以访问

$ chmod 600 /mnt/swapfile

配置Swap文件

$ mkswap /mnt/swapfile

开启Swap

$ swapon /mnt/swapfile

关闭 Swap:
​shell

$ swapoff -a

$ swapoff -a

关闭后再打开,是一种常用的 Swap 空间清理方式:
​shell

$ swapoff -a && swapon -a

$ swapoff -a && swapon -a

5 案例

模拟大文件读取:
​shell

写入空设备,实际上只有磁盘的读请求

$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

写入空设备,实际上只有磁盘的读请求

$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

5.1 查看系统内存

使用 sar 指令查看内存使用情况:
​shell

间隔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

04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20

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

04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0

04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:44:07 8384508 4096 0.05 52 1.27

间隔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

04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20

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

04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0

04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:44:07 8384508 4096 0.05 52 1.27

sar 数量两个表格,第一个是内存使用情况,第二个是 Swap 使用情况。

kbcommit:当前系统负载需要的内存。为了保证内存不溢出,对需要内存的估计值。%commit 是相对于总内存的百分比。
kbactive:活跃内存,最新使用过,一般不会被系统回收。
kbinactive:不活跃内存,不常访问,可能会被系统回收。

%memused 从 23.5% 增长到 98.13%,且主要被 kbbuffers 使用,具体为:

刚开始,kbmemfree 不断减少,kbbuffers 不断增大,说明剩余内存不断分配给缓冲区。
一段时间后,剩余内存已经很少了,Swap 使用增大,而缓冲区和剩余内存在小范围内波动。

5.2 查看进程情况

是哪些进程导致了缓冲区增大?查看进程的缓存情况:
​shell

$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
18280 root python 22 0 0 100.0% 0.0%
18279 root dd 41088 41022 0 50.0% 50.0%

$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
18280 root python 22 0 0 100.0% 0.0%
18279 root dd 41088 41022 0 50.0% 50.0%

可以看到 dd 的读写请求命中率只有 50%,未命中的缓存页数为 41022,说明这是 dd 导致了缓冲区的使用升高。

缓冲区占用了大多数内存,属于可回收内存,内存不够时应该先回收缓冲区,为什么 Swap 会升高呢?

观察剩余内存、内存阈值以及匿名页和文件页的活跃情况:
​shell

-d 表示高亮变化的字段

-A 表示仅显示Normal行以及之后的15行输出

$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone Normal
pages free 21328

    min      14896
    low      18620
    high     22344
    spanned  1835008
    present  1835008
    managed  1796710
    protection: (0, 0, 0, 0, 0)
  nr_free_pages 21328
  nr_zone_inactive_anon 79776
  nr_zone_active_anon 206854
  nr_zone_inactive_file 918561
  nr_zone_active_file 496695
  nr_zone_unevictable 2251
  nr_zone_write_pending 0

-d 表示高亮变化的字段

-A 表示仅显示Normal行以及之后的15行输出

$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone Normal
pages free 21328

    min      14896
    low      18620
    high     22344
    spanned  1835008
    present  1835008
    managed  1796710
    protection: (0, 0, 0, 0, 0)
  nr_free_pages 21328
  nr_zone_inactive_anon 79776
  nr_zone_active_anon 206854
  nr_zone_inactive_file 918561
  nr_zone_active_file 496695
  nr_zone_unevictable 2251
  nr_zone_write_pending 0

会发现,剩余内存(pages free)在一个范围内变化。当小于 low 时,又突然会增大到高于 high。

可以推测,内存的回收和缓存再次分配的循环往复导致了剩余内存和缓冲区的波动:

当剩余内存小于 low 时,系统会回收缓存和匿名内存。其中,缓存的回收会导致缓冲区减少,匿名内存的回收会导致 Swap 的使用增大。
而由于 dd 还在继续,剩余内存会继续分配给缓存,导致剩余内存减少,缓冲区增大。

多次运行 dd 和 sar,会发现有时候 Swap 使用的多,有时候 Swap 使用的少,而缓冲区的波动较大。可见,系统回收不同类型的内存的倾向不明显,可查看 swampiness:
​shell

$ cat /proc/sys/vm/swappiness
60

$ cat /proc/sys/vm/swappiness
60

该值比较中和,系统会根据情况回收匿名页或文件页。

找到了 Swap 变化的原因,可继续看下 Swap 影响了哪些程序:
​shell

按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量

$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量

$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

通常,降低 Swap 的使用可提高系统性能,具体方式为:

禁止 Swap。
降低 swappiness
对于延时敏感应用,使用 mlock() 和 mlockall() 锁定内存,禁止内存换出。

参考

倪朋飞. Linux 性能优化实战.

  • 在应用程序中通过系统调用 fsync 将脏页写入磁盘。
  • 交给系统,由内核线程 pdflush 负责脏页的刷新。
  • 对于匿名页的回收,即应用程序动态分配的堆内存,可通过 Swap 机制写到磁盘,然后释放那些内存,再次访问这些内存时,再次从磁盘读入即可。

1 Swap 原理

Swap 将一块磁盘空间或本地文件当作内存使用。

换出:将进程暂时不用的内存数据存储到磁盘,并释放这些数据占用的内存。

换入:在进程再次访问这些内存时,把它们从磁盘读入内存。

Linux 在什么时候需要回收内存:

  • 有新的大块内存申请,剩余内存不足时,需要回收一部分内存,称为直接内存回收。
  • 有一个专门的内核线程 kswapd0 定期回收内存。kswapd0 定义了三个阈值,这三个阈值可在 /proc/zoneinfo 中调整。当剩余内存位于 pages_min 和 pages_low 之间时,kswapd0 会进行内存回收,直到剩余内存大与 pages_hig。img

2 NUMA 与 Swap

NUMA 架构下,多个处理器被分配到不同的 Node 上,且每个 Node 都有自己的本地内存空间。

查看 NUMA:

$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

显示系统中只有一个 Node,即 Node 0,CPU 0 和 CPU 1 都在该 Node 上。Node 0 内存大小为 7977MB,剩余内存大小为 4416MB。

当某个 Node 的内存不足时,系统可以从其它 Node 寻找空闲内存,也可以从本地回收空闲内存,可通过 /proc/sys/vm/zone_reclaim_mode 调整。

  • 默认是 0,表示既可以从其它 Node 寻找空闲内存,也可以从本地回收空闲内存。
  • 1,2,4 表示只可回收本地空闲内存,2 表示通过回写脏数据回收内存,4 表示通过 Swap 机制回收内存。

3 swappiness

Linux 提供了 /proc/sys/vm/swappiness 选项来调整 Swap 积极程度。swappiness 范围为 [0, 100],数值越大,越积极使用 Swap;反之,更倾向于回收文件页。

4 开启 Swap

$ free
             total        used        free      shared  buff/cache   available
Mem:        8169348      331668     6715972         696     1121708     7522896
Swap:             0           0           0

可以看出,Swap 的 total 为 0,说明没有设置 Swap。

设置并开启 Swap:

# 创建Swap文件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根用户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap文件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile

关闭 Swap:

$ swapoff -a

关闭后再打开,是一种常用的 Swap 空间清理方式:

$ swapoff -a && swapon -a 

5 案例

模拟大文件读取:

# 写入空设备,实际上只有磁盘的读请求
$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

5.1 查看系统内存

使用 sar 指令查看内存使用情况:

# 间隔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

04:39:57    kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
04:39:58      6184472   6807064   1984836     24.30    772768     67380   1691736     10.22    847932    874224        20

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

…


04:44:06    kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
04:44:07       152780   6525716   8016528     98.13   6530440     51316   1691736     10.22    867124   6869332         0

04:44:06    kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
04:44:07      8384508      4096      0.05        52      1.27

sar 数量两个表格,第一个是内存使用情况,第二个是 Swap 使用情况。

  • kbcommit:当前系统负载需要的内存。为了保证内存不溢出,对需要内存的估计值。%commit 是相对于总内存的百分比。
  • kbactive:活跃内存,最新使用过,一般不会被系统回收。
  • kbinactive:不活跃内存,不常访问,可能会被系统回收。

%memused 从 23.5% 增长到 98.13%,且主要被 kbbuffers 使用,具体为:

  • 刚开始,kbmemfree 不断减少,kbbuffers 不断增大,说明剩余内存不断分配给缓冲区。
  • 一段时间后,剩余内存已经很少了,Swap 使用增大,而缓冲区和剩余内存在小范围内波动。

5.2 查看进程情况

是哪些进程导致了缓冲区增大?查看进程的缓存情况:

$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
   18280 root     python                 22        0        0     100.0%       0.0%
   18279 root     dd                  41088    41022        0      50.0%      50.0%

可以看到 dd 的读写请求命中率只有 50%,未命中的缓存页数为 41022,说明这是 dd 导致了缓冲区的使用升高。

缓冲区占用了大多数内存,属于可回收内存,内存不够时应该先回收缓冲区,为什么 Swap 会升高呢?

观察剩余内存、内存阈值以及匿名页和文件页的活跃情况:

# -d 表示高亮变化的字段
# -A 表示仅显示Normal行以及之后的15行输出
$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone   Normal
  pages free     21328
        min      14896
        low      18620
        high     22344
        spanned  1835008
        present  1835008
        managed  1796710
        protection: (0, 0, 0, 0, 0)
      nr_free_pages 21328
      nr_zone_inactive_anon 79776
      nr_zone_active_anon 206854
      nr_zone_inactive_file 918561
      nr_zone_active_file 496695
      nr_zone_unevictable 2251
      nr_zone_write_pending 0

会发现,剩余内存(pages free)在一个范围内变化。当小于 low 时,又突然会增大到高于 high。

可以推测,内存的回收和缓存再次分配的循环往复导致了剩余内存和缓冲区的波动:

  • 当剩余内存小于 low 时,系统会回收缓存和匿名内存。其中,缓存的回收会导致缓冲区减少,匿名内存的回收会导致 Swap 的使用增大。
  • 而由于 dd 还在继续,剩余内存会继续分配给缓存,导致剩余内存减少,缓冲区增大。

多次运行 dd 和 sar,会发现有时候 Swap 使用的多,有时候 Swap 使用的少,而缓冲区的波动较大。可见,系统回收不同类型的内存的倾向不明显,可查看 swampiness:

$ cat /proc/sys/vm/swappiness
60

该值比较中和,系统会根据情况回收匿名页或文件页。

找到了 Swap 变化的原因,可继续看下 Swap 影响了哪些程序:

# 按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

通常,降低 Swap 的使用可提高系统性能,具体方式为:

  • 禁止 Swap。
  • 降低 swappiness
  • 对于延时敏感应用,使用 mlock() 和 mlockall() 锁定内存,禁止内存换出。

参考

倪朋飞. Linux 性能优化实战.