内存、虚拟内存与 OOM

9 阅读6分钟

Linux 内存:怎么看、底层机制与 OOM

都知道 free -h 查看内存,但是真的看懂输出了吗?虚拟内存是什么?OOM 是什么? 本节讲的是怎么看内存,以及系统内存的底层原理与机制。


一、内存怎么看

free -h
              total        used        free      shared  buff/cache   available
Mem:           15Gi       6.2Gi       1.1Gi       412Mi       8.4Gi       8.9Gi
Swap:         4.0Gi          0B       4.0Gi

这么多字段,哪个字段才能看内存?

free 字段显示只有 1.1Gi,是不是只有 1Gi 了?不是! 还是说要看 used 字段?也不是! 那 buff/cache 占比这么大,是不是说明内存不足了?这是缓存(Linux 拿空闲内存缓存文件数据加速),应用需要时会自动回收。

其实真正要看的是 available 字段,真正缺内存的信号是 si/so —— 如果 vmstat 里 si/so 持续不为 0,才说明内存真的紧张。至于 Swap 是什么,后续会讲。

free 各字段的含义:

字段含义
total物理内存总量
used已用(= total − free − buff/cache)
free完全没用的空闲(很小是正常的)
sharedtmpfs / 共享内存占用
buff/cache块设备缓冲 + 文件页缓存(可回收)
available应用真正还能拿到的内存(看这个)

1. 能不能手动释放 cache?

能但生产慎用:echo 3 > /proc/sys/vm/drop_caches。它是缓存,不是泄漏;释放后系统还要重新读盘,性能反而变差。

2. 内存不够会有什么现象

  • 系统变慢(频繁换页到 swap,wa 变高)
  • 进程被 OOM Killer 杀掉 → dmesg | grep -i oom 能查到
  • 容器里的进程被杀 → 状态显示 OOMKilled
  • 新进程 fork 失败(Cannot allocate memory)
  • 日志里出现 "Out of memory: Killed process ..."

3. 用 top 怎么排查进程的内存

这个专门写一节怎么运用 top 指令的,等待更新


二、虚拟内存与缺页中断(机制层)

涉及的概念

概念一句话
虚拟内存进程看到的地址空间;提供隔离、连续地址、可换出到 swap、可共享
物理内存真正装在机器上的内存条
页(page)内存管理的最小单位,通常 4KB
页表 / TLB虚拟地址 → 物理地址的映射表 / 它的高速缓存
匿名页没有对应文件的页(堆、栈、malloc 出来的内存)→ 回收时只能写 swap
文件页对应磁盘文件的页(程序、数据、page cache)→ 可直接丢弃再读回

1. 为什么需要虚拟内存

① 隔离:进程 A 碰不到进程 B 的内存; ② 连续假象:不必要求物理内存连续,解决碎片; ③ 超量使用:可以换出到 swap、可以按需加载。

2. 什么是缺页中断

  1. 进程访问一个虚拟地址
  2. CPU 查页表:发现这一页"不在物理内存"(页表项无效)
  3. 触发缺页异常(page fault),陷入内核
  4. 内核判断属于哪种情况:
    • 从来没分配过 → 分配一个物理页(可能填零)
    • 页在 page cache / 已在内存但没建立映射 → 建立映射
    • 页被换出到 swap 或存在文件里 → 发起磁盘 IO 读回来
  5. 更新页表(并刷新 TLB)
  6. 返回用户态,重新执行刚才那条指令 —— 这次就成功了

根据页是否在物理内存里,分成了两种缺页:

类型定义代价典型场景
minor(次要)缺页页其实已经在物理内存里,只是还没建立映射小(不涉及磁盘)首次访问 malloc 的堆、mmap 建立映射、写时复制 COW
major(主要)缺页页不在内存,必须读磁盘(swap 或文件)大(一次磁盘 IO)进程被换出后又被唤醒、首次读大文件

性能经验:major fault 持续很高 = 在疯狂读磁盘 / swap,系统会明显变卡(这时候 si/so、wa 一般也高)。

3. 内存映射(mmap)是什么?有什么用?

mmap = 把一个文件(或一块匿名内存)映射到进程的虚拟地址空间。 之后进程"读写内存"就等于"读写文件",不用 read/write 系统调用搬运 —— Linux 的一切皆文件。

类型说明典型用途
文件映射映射一个文件,页来自 page cache加载动态库(libc.so)、大文件随机读写
匿名映射不关联文件,只能放内存 / swapmalloc 的大块内存、共享内存
私有(MAP_PRIVATE)写时复制(COW),改动不回写文件加载程序段
共享(MAP_SHARED)多个进程看到同一份改动进程间共享内存(IPC)

它的好处:

  1. 快:少一次用户态 ↔ 内核态的数据拷贝(对大文件尤其明显)
  2. 共享:多个进程映射同一个文件 / 同一段匿名内存,天然共享
  3. 按需加载:只有真正访问到的页才读进来(配合缺页中断)

联系前面:free 里的 buff/cache 很大一部分就是这些文件映射带来的 page cache —— 它是缓存,不是内存不够。


三、swap 与 OOM

1. 什么是 swap?什么情况会用到 swap

swap = 磁盘/SSD 上划出来的一块空间,作为内存的后备。
内存紧张时,内核会先回收"文件页"(直接丢掉,需要时再读回来),
再回收不动就只好把"匿名页"写到 swap(换出),等进程再用到时再读回来(major fault)
free -h ; swapon --show ; cat /proc/swaps  # 查看 swap
vmstat 1 5                                 # 看 si/so
sysctl vm.swappiness                       # 看当前值

什么情况下"会用 swap":① 内存压力大(available 低于水位);② swappiness 偏高;③ 应用有大量"长时间不访问的匿名页";④ 内存限制(容器 / cgroup)被触发。 注意:swap 用起来不是"故障",但 swap 频繁读写就是性能灾难(机械盘随机 IO 比内存慢几万倍)。很多数据库 / 中间件要求关掉 swap 或调到很低。

生产环境要不要开 swap?

普通服务器建议留一点(内存的 12 倍或按需,比如 48G)当缓冲,防止一次内存尖峰直接触发 OOM; 但数据库 / 中间件(MySQL、Redis)和 K8s 节点一般要求关掉或把 vm.swappiness 调到 1~10,避免换页导致延迟抖动。

2. 什么是 OOM?什么导致了 OOM?

物理内存 + swap 都回收不出空间,新的内存申请无法满足
  → 内核的 OOM Killer 介入,按 oom_score 挑一个进程杀掉,回收它的内存
  → 目的是保住系统本身不崩
类别具体原因
应用层① 内存泄漏(只申请不释放) ② 并发/流量激增,瞬时申请太多 ③ 一次性加载大文件、大查询结果集 ④ JVM/进程堆参数设得比物理内存还大(-Xmx 超配)
内核参数⑤ overcommit(允许超量分配,vm.overcommit_memory=1 时更容易 OOM) ⑥ swappiness / 水位设置不当,缓存回收不及时
资源规划⑦ swap 太小或没开,没有缓冲 ⑧ 多个服务挤在一台机器上,总量超过物理内存 ⑨ 容器 / cgroup 内存 limit 设太小(容器里报 OOMKilled)
不可回收⑩ 内核页、共享内存段、锁定内存(mlock)太多,page cache 回收不动
OOM Killer 怎么挑人?

等待更新

出了 OOM 怎么查?

等待更新


四、内存泄漏排查

等待更新