一文明白 Linux 内存 Page Cache 和 Swap

2,440 阅读5分钟

前言

先来看下本机系统内存使用情况:数值默认单位为 KB

# 笔记本本机 16G 内存,Ubuntu 18
$ free
              total        used        free      shared  buff/cache   available
Mem:       16306984    13655780     1205560      576992     1445644     1748628
Swap:       2097148     1620224      476924
​
# -h 适配可读的单位。-m 即 MB 为单位
$ free -h
              total        used        free      shared  buff/cache   available
Mem:            15G         13G        320M        649M        1.6G        1.0G
Swap:          2.0G        1.6G        435M
  • Total(全部) : 总内存大小
  • Used(已用) : 已使用的内存大小,包含共享内存
  • Free(未用) : 未使用内存的大小
  • Shared(共享): 共享内存的大小
  • Buff/Cache(缓存):块设备缓存区 和 文件缓存大小
  • Available(可用) : 新进程可用内存的大小(未使用内存 + 可回收的内存)
  • Swap(交换区) :把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。

关于 Buff/Cache ,可以通过 man free 手册来查看说明:

内存-2022-07-2916-06-21.png

  • Buffers :是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。

  • Cache: 是 Page Cache 页缓存 和 Slab 用到的内存,对应的是 /proc/meminfo 中的 CachedSReclaimable 之和。

    • Page Cache 页缓存:缓存从文件读取的数据。
    • Slab:用于记录可回收部分 和 不可回收部分。

由上,并结合平时开发经验,推测内存组成部分:堆内存、堆外内存、Page Cache、其他

内存-2022-07-2916-30-03.png

问题:在 64位系统下,虚拟地址空间中 内核空间 和 用户空间 均占 128T,可对应的空间在物理内存中占多少?

这其实没有一个明确的答案,因为 内核空间 和 用户空间 共享同一个物理内存。 但我们平时开发时(KafkaES)经常接触到 Page Cache,同时希望能分配更多内存给 Page Cache。 要么限制 Page Cache 的大小,要么限制应用程序的大小。 一般我们都选择限制应用程序,举个栗子: 安装 ES 在 8C16G 机子下,设置最大和最小堆内存为 8G,-Xms8g -Xmx8g目的就是留一半内存给 Page Cache


下面着重来介绍 Page CacheSwap


Page Cache 页缓存

Page cache:也叫页缓冲文件缓冲也叫 ES 中的 filesystem cache

问题:为什么需要 Page Cache ?

  • 回答:加速对文件内容的访问。减少磁盘I/O,提升应用的 I/O 速度。

举个栗子:写文件

调用 write(2)函数,写入 Page Cache后就返回,内核会自动刷盘(把 Page Cache 中数据写入磁盘)。

来看张神图,应用程序产生 Page Cache 的逻辑示意图:

pagecache-2022-07-2918-18-13.png

从图中可知:

  1. Page Cache 是内核管理的内存,属于内核不属于用户。
  2. 有两种方式可以产生 Page Cache:标准I/OBuffered I/O) 和 MMAPMemory-Mapped I/O
  3. Linux I/O 有 3 种方式:标准I/OMMAPDirect I/O

要分析此图,我们还需要先了解 Linux IO 栈:

  1. 虚拟文件系统(VFS :对各种文件系统的一个抽象,它为各种文件系统提供了一个通用的接口。
  2. 块层(Block I/O :管理块设备的 IO 队列,对 IO 请求进行合并、排序。
  3. 设备驱动(Device Driver :操作存储介质。

pagecache-2022-07-2923-18-57.png

再看 Linux I/O 3 种方式,这里做了简化:

问题:为什么上张图里有 VFS,这图改为 File System文件系统? 回答:VFS 就像接口,定义好接口,其他人就来实现就行。File System文件系统更为具体化。 举个栗子:open() 函数,可以以特定的文件描述符打开某一个文件。 使用:fd = open("myfile", O_RDWR | O_CREAT | O_SYNC | O_DIRECT, S_IRUSR | S_IWUSR);

  • O_DIRECT:无缓冲的输入、输出。对应 Direct I/O
  • O_SYNC:以同步 IO 方式打开文件。


这里着重再介绍下 Direct I/O
是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。

想要实现直接 I/O,需要在系统调用中,指定 O_DIRECT 标识。

官方文档

pagecache-2022-07-3011-42-26.png

官方文档中的说明,主要体现了 3 点:

  1. 缺点: 这种方式会降低性能。(磁盘读写、磁盘寻道等。)
  2. 特殊应用场景: 应用程序自己管理缓存,文件I/O直接对接用户空间缓冲区。(对开发者能力要求高)
  3. 不保证同步: 不能确保每次写入都同步数据到磁盘,还是需要设置 O_SYNC 标识或者手动 fsync

TipsJava 基本库不支持 Direct I/O,若想使用需引入 JNA 包。


Swap 交换区

Swap 说白了就是把一块磁盘空间或者一个本地文件(以下讲解以磁盘为例),当成内存来使用。

Swap 把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。

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

问题:为什么一些中间件都建议关闭 Swap

  1. 性能问题: 例如 ES,与 JVMGC 相关。GC会遍历所用到的堆的内存,如果有部门内存被 Swap 到磁盘,那么 GC 遍历时候就会去查磁盘,磁盘 IO 又很慢,这就会导致应用程序 STW 假死一段时间。

    ES文档

  2. 管理问题: 例如 K8s,开启 Swap后通过 Cgroups 设置的内存上限就会失效。