You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 19 | 案例篇为什么系统的Swap变高了
你好,我是倪朋飞。
上一节,我通过一个斐波那契数列的案例,带你学习了内存泄漏的分析。如果在程序中直接或间接地分配了动态内存,你一定要记得释放掉它们,否则就会导致内存泄漏,严重时甚至会耗尽系统内存。
不过,反过来讲,当发生了内存泄漏时,或者运行了大内存的应用程序,导致系统的内存资源紧张时,系统又会如何应对呢?
在内存基础篇我们已经学过,这其实会导致两种可能结果,内存回收和 OOM 杀死进程。
我们先来看后一个可能结果,内存资源紧张导致的 OOMOut Of Memory相对容易理解指的是系统杀死占用大量内存的进程释放这些内存再分配给其他更需要的进程。
这一点我们前面详细讲过,这里就不再重复了。
接下来再看第一个可能的结果,内存回收,也就是系统释放掉可以回收的内存,比如我前面讲过的缓存和缓冲区,就属于可回收内存。它们在内存管理中,通常被叫做**文件页(**File-backed Page
大部分文件页,都可以直接回收,以后有需要时,再从磁盘重新读取就可以了。而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放。
这些脏页,一般可以通过两种方式写入磁盘。
* 可以在应用程序中,通过系统调用 fsync ,把脏页同步到磁盘中;
* 也可以交给系统,由内核线程 pdflush 负责这些脏页的刷新。
除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取。
除了文件页外,还有没有其他的内存可以回收呢?比如,应用程序动态分配的堆内存,也就是我们在内存管理中说到的**匿名页**Anonymous Page是不是也可以回收呢
我想,你肯定会说,它们很可能还要再次被访问啊,当然不能直接回收了。非常正确,这些内存自然不能直接释放。
但是,如果这些内存在分配后很少被访问,似乎也是一种资源浪费。是不是可以把它们暂时先存在磁盘里,释放内存给其他更需要的进程?
其实这正是Linux的Swap机制。Swap把这些不常访问的内存先写到磁盘中然后释放这些内存给其他更需要的进程使用。再次访问这些内存时重新从磁盘读入内存就可以了。
在前几节的案例中我们已经分别学过缓存和OOM的原理和分析。那Swap 又是怎么工作的呢因为内容比较多接下来我将用两节课的内容带你探索Swap的工作原理以及Swap升高后的分析方法。
今天我们先来看看Swap究竟是怎么工作的。
## Swap原理
前面提到Swap说白了就是把一块磁盘空间或者一个本地文件以下讲解以磁盘为例当成内存来使用。它包括换出和换入两个过程。
* 所谓换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
* 而换入,则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。
所以你看Swap其实是把系统的可用内存变大了。这样即使服务器的内存不足也可以运行大内存的应用程序。
还记得我最早学习Linux操作系统时内存实在太贵了一个普通学生根本就用不起大的内存那会儿我就是开启了Swap来运行Linux桌面。当然现在的内存便宜多了服务器一般也会配置很大的内存那是不是说Swap就没有用武之地了呢
当然不是。事实上,内存再大,对应用程序来说,也有不够用的时候。
一个很典型的场景就是即使内存不足时有些应用程序也并不想被OOM杀死而是希望能缓一段时间等待人工介入或者等系统自动释放其他进程的内存再分配给它。
除此之外我们常见的笔记本电脑的休眠和快速开机的功能也基于Swap 。休眠时,把系统的内存存入磁盘,这样等到再次开机时,只要从磁盘中加载内存就可以。这样就省去了很多应用程序的初始化过程,加快了开机速度。
话说回来既然Swap是为了回收内存那么Linux到底在什么时候需要回收内存呢前面一直在说内存资源紧张又该怎么来衡量内存是不是紧张呢
一个最容易想到的场景就是,有新的大块内存分配请求,但是剩余内存不足。这个时候系统就需要回收一部分内存(比如前面提到的缓存),进而尽可能地满足新内存请求。这个过程通常被称为**直接内存回收**。
除了直接内存回收,还有一个专门的内核线程用来定期回收内存,也就是**kswapd0**。为了衡量内存的使用情况kswapd0定义了三个内存阈值watermark也称为水位分别是
页最小阈值pages\_min、页低阈值pages\_low和页高阈值pages\_high。剩余内存则使用 pages\_free 表示。
这里,我画了一张图表示它们的关系。
![](https://static001.geekbang.org/resource/image/c1/20/c1054f1e71037795c6f290e670b29120.png)
kswapd0定期扫描内存的使用情况并根据剩余内存落在这三个阈值的空间位置进行内存的回收操作。
* 剩余内存小于**页最小阈值**,说明进程可用内存都耗尽了,只有内核才可以分配内存。
* 剩余内存落在**页最小阈值**和**页低阈值**中间说明内存压力比较大剩余内存不多了。这时kswapd0会执行内存回收直到剩余内存大于高阈值为止。
* 剩余内存落在**页低阈值**和**页高阈值**中间,说明内存有一定压力,但还可以满足新内存请求。
* 剩余内存大于**页高阈值**,说明剩余内存比较多,没有内存压力。
我们可以看到,一旦剩余内存小于页低阈值,就会触发内存的回收。这个页低阈值,其实可以通过内核选项 /proc/sys/vm/min\_free\_kbytes 来间接设置。min\_free\_kbytes 设置了页最小阈值,而其他两个阈值,都是根据页最小阈值计算生成的,计算方法如下
```
pages_low = pages_min*5/4
pages_high = pages_min*3/2
```
## NUMA与Swap
很多情况下,你明明发现了 Swap 升高,可是在分析系统的内存使用时,却很可能发现,系统剩余内存还多着呢。为什么剩余内存很多的情况下,也会发生 Swap 呢?
看到上面的标题,你应该已经想到了,这正是处理器的 NUMA Non-Uniform Memory Access架构导致的。
关于 NUMA我在 CPU 模块中曾简单提到过。在 NUMA 架构下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间。
而同一个 Node 内部的内存空间实际上又可以进一步分为不同的内存域Zone比如直接内存访问区DMA、普通内存区NORMAL、伪内存区MOVABLE如下图所示
![](https://static001.geekbang.org/resource/image/be/d9/be6cabdecc2ec98893f67ebd5b9aead9.png)
先不用特别关注这些内存域的具体含义,我们只要会查看阈值的配置,以及缓存、匿名页的实际使用情况就够了。
既然 NUMA 架构下的每个 Node 都有自己的本地内存空间,那么,在分析内存的使用时,我们也应该针对每个 Node 单独分析。
你可以通过 numactl 命令,来查看处理器在 Node 的分布情况,以及每个 Node 的内存使用情况。比如,下面就是一个 numactl 输出的示例:
```
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...
```
这个界面显示,我的系统中只有一个 Node也就是Node 0 ,而且编号为 0 和 1 的两个 CPU 都位于 Node 0 上。另外Node 0 的内存大小为 7977 MB剩余内存为 4416 MB。
了解了 NUNA 的架构和 NUMA 内存的查看方法后,你可能就要问了这跟 Swap 有什么关系呢?
实际上,前面提到的三个内存阈值(页最小阈值、页低阈值和页高阈值),都可以通过内存域在 proc 文件系统中的接口 /proc/zoneinfo 来查看。
比如,下面就是一个 /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分别是活跃和非活跃的文件页数。
从这个输出结果可以发现,剩余内存远大于页高阈值,所以此时的 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当[剩余内存+文件页小于页高阈值](https://www.kernel.org/doc/Documentation/sysctl/vm.txt)时还是会发生Swap。
清楚了 Swap 原理后,当遇到 Swap 使用变高时,又该怎么定位、分析呢?别急,下一节,我们将用一个案例来探索实践。
## 小结
在内存资源紧张时Linux通过直接内存回收和定期扫描的方式来释放文件页和匿名页以便把内存分配给更需要的进程使用。
* 文件页的回收比较容易理解,直接清空,或者把脏数据写回磁盘后再释放。
* 而对匿名页的回收需要通过Swap换出到磁盘中下次访问时再从磁盘换入到内存中。
你可以设置/proc/sys/vm/min\_free\_kbytes来调整系统定期回收内存的阈值也就是页低阈值还可以设置/proc/sys/vm/swappiness来调整文件页和匿名页的回收倾向。
在 NUMA 架构下,每个 Node 都有自己的本地内存空间,而当本地内存不足时,默认既可以从其他 Node 寻找空闲内存,也可以从本地内存回收。
你可以设置 /proc/sys/vm/zone\_reclaim\_mode 来调整NUMA本地内存的回收策略。
## 思考
最后,我想请你一起来聊聊你理解的 SWAP。我估计你以前已经碰到过 Swap 导致的性能问题,你是怎么分析这些问题的呢?你可以结合今天讲的 Swap 原理,记录自己的操作步骤,总结自己的解决思路。
欢迎在留言区和我讨论,也欢迎把这篇文章分享给你的同事、朋友。我们一起在实战中演练,在交流中进步。