文件页:代表可回收内存,文件页的大部分可以直接回收,以后有需要时,再从磁盘重新读取;而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放
脏页一般以两个方式写入磁盘:
- 在应用程序中,通过系统调用 fync,把脏页同步到磁盘中
- 由内核线程 pdflush 负载这些脏页的刷新
匿名页:应用程序动态分配的堆空间,使用 swap 机制回收
Swap 原理
Swap 简单来说就是把一块磁盘空间或者一个本地文件夹,当成内存来使用。它包括换出和换入两个过程:
- 换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存
- 换入,则是把进程再次访问这些内存的时候,把它们从磁盘读到内存中来
常见的笔记本电脑的休眠和快速开机功能,也基于 Swap。休眠时,把系统的内存存入磁盘,这样等到再次开机时,只要从磁盘中加载内存就可以。这样省去了很多应用程序的初始化过程,加快了开机速度。
内存回收的时机:
1、直接内存回收:当有新的大块内存分配请求,但是剩余内存不足,这个时候系统就需要回收一部分内存
2、内核线程 kswapd0来定期回收内存,它定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(pages_min)、页最低阈值(pages_low)和页最高阈值(pages_high)。剩余内存,则使用 pages_free 表示
kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的会后操作:
- 剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配空间
- 剩余内存落在页最小阈值和页最低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止
- 剩余内存落在页最低阈值和页最高阈值中间,说明内存有一定压力,但还可以满足新压力请求
- 剩余内存大于页内存阈值,说明剩余内存比较多,没有内存压力
页低阈值是由内核选项 /proc/sys/vm/min_free_kbytes 设置,其他两个阈值,都是根据页最小阈值计算生成的
NUMA 和 Swap
在 NUMA 架构下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间。而同一个 Node 内部的内存空间,实际上又可以在进一步分为不同的内存域(Zone),比如直接内存访问区(DMA)、普通内存区(NORMAL)、伪内存区(MOVABLE)等,如下图所示:
使用 numactl 命令查看 Node 的分布情况
1 | $ numactl --hardware |
前面提到的三个内存阈值(页最小阈值、页低阈值和页高阈值),都可以通过内存域在 proc 文件系统中的接口 /proc/zoneinfo 来查看
1 | $ cat /proc/zoneinfo| head -n 20 |
主要指标包括:
- 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 |
|
执行 free 看到 swap 添加成功
1 | $ free |
执行 dd 命令,模拟大文件的读取
1 | dd if=/dev/vdb1 of=/dev/null bs=1G count=400 |
执行 sar 查看内存和 swap 指标
1 | $ sar -rS 3 |
可以看到,总的内存使用率(%memused)在不断增长,从开始的 25% 一直涨到了 99%,并且主要内存都被缓冲区(kbbuffers)占用,大致的变化过程为:
- 刚开始,剩余内存(kbmemfree)不断减少,而缓冲区(kbbuffers)则不断增大,由此可知,剩余内存不断分配给了缓冲区
- 一段时间后,剩余内存已经很小了,而缓存区占用了大部分内存。此时,Swap 的使用开始逐渐增大,缓冲区和剩余内存则只在小范围内波动
为什么 Swap 会升高呢?(按理来说,应该先回收缓冲区的内存,这属于可回收内存),观察 /proc/zoneinfo 指标如下
1 | $ watch -d grep -A 15 'Normal' /proc/zoneinfo |
可以看到,剩余内存(pages free)在一个很小范围内不停地波动。当它小于页低阈值(pages low)时,又会突然增大到一个大于页高阈值(pages high)的数值
- 当剩余内存小于页低阈值时,系统会回收一些缓存和匿名内存,使剩余内存增大。其中,缓存的回收导致 sar 中的缓冲区减少,而匿名内存的回收导致了 Swap 的使用增大。其中,缓存的回收导致 sar 中的缓冲区减少,而匿名内存的回收导致了 Swap 的使用增大。
- 同事由于 dd 还在继续,剩余内存又会重新分配给缓存,导致剩余内存减少,缓冲区增大
利用 proc 文件系统,可以查看 Swap 换出的虚拟内存大小,它保存在 /proc/pid/status 中 VmSwap
1 | $ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head |
可以看到,使用 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 |