如何分析Linux内存性能问题

Linux内存资源分析

Cache与Buffer的区别

1. 物理流向:数据的搬运

  • Disk → Buffer:数据从磁盘读到内存的Buffer区域。这个过程通常由DMA(直接内存访问)完成,不占用CPU。
  • Buffer → Cache:数据从Buffer拷贝到Cache。这一步的“拷贝”有时是显式的(如传统I/O),有时是逻辑上的共享(如mmap)。
  • Cache → CPU:CPU直接从Cache读取数据进行计算。

2. 核心区别:Buffer vs Cache

这是理解这张图的关键,也是最容易混淆的地方:

  • Buffer(缓冲区)“为了写”
    • 它的主要作用是缓解速度差异,或者攒一批数据再处理
    • 在你这张图里,Buffer紧邻Disk。它的存在是为了积攒从磁盘来的数据流,防止CPU频繁被打断去处理单个字节;或者反过来,将要写入磁盘的数据攒成一大块再一次性写入。
    • 对象是磁盘(块设备)
  • Cache(缓存))“为了读”
    • 它的主要作用是避免重复计算/读取
    • 它的存在是因为CPU下一次很可能还要用同一份数据,直接放在Cache里,下次CPU来取的时候瞬间就能拿到,不用再去底层(Buffer甚至Disk)重新搬一遍。
    • 对象是数据(文件内容)服务对象是CPU/应用程序

3. 业务流程举例

假设CPU要读取一个文件:

  1. 查Cache:CPU先去Cache里看,有没有我要的数据?
  2. Cache Miss:Cache里没有。
  3. 去Buffer找:Cache知道这个数据在磁盘的哪个位置,它会让Buffer去把这块数据从Disk拉进来。
  4. Buffer读盘:Buffer向磁盘发起I/O请求,磁盘把数据扇区读入Buffer。
  5. 填入Cache:Buffer把数据转交给Cache(Page Cache)。
  6. CPU处理:CPU从Cache中取走数据。

4. 为什么要把Buffer和Cache分开画?

在早期的操作系统(或非常底层的嵌入式系统)中,Buffer和Cache在内存里是不同的区域,职责分明。

虽然在现代Linux系统中,你通过free命令看到的buff/cache已经很难严格区分,很多场景下它们共享同一片内存池(Page Cache既充当缓存也充当缓冲区),但在概念架构分析或性能调优(如直接I/O绕过Cache、裸设备读写绕过Buffer)时,这种区分依然非常重要。

如何清除cache与buffer
1、仅清除页面缓存PageCache方法:
echo 1 > /proc/sys/vm/drop_caches
2、清除目录项和inode节点:
echo 2 > /proc/sys/vm/drop_caches
3、清除页面缓存、目录项和inode节点:
echo 3 > /proc/sys/vm/drop_caches

内存不足时,系统回收内存步骤

这张图完整展示了Linux内存回收机制的四个层级,从温和的异步回写,到强制的同步回收,再到极端情况下的杀进程保系统。

这是一个由轻到重、逐级递进的内存紧张处理流程。我们按顺序拆解:


第一层:脏页面写回 —— 腾出“干净”内存

背景
应用程序修改了文件(如写入日志),数据先存在Page Cache里。此时这些页面是脏的——内存里有最新数据,磁盘里还是旧数据。

目的
把脏页面刷到磁盘,把它们变成干净页面。干净页面可以直接回收(丢弃),或者被其他进程复用。

关键参数

  • vm.dirty_ratio:脏页占系统内存的绝对上限(默认20%)。到达此值后,所有后续写入操作都会阻塞,强制刷盘。
  • vm.dirty_background_ratio:脏页达到此比例(默认10%),内核后台异步启动回写线程,不阻塞应用程序。

在图中位置
这是第一道防线。内存还没到极紧张状态,先加速把脏数据落盘,把“脏”变“干净”。


第二层:页面缓存清理 —— 回收“冷”数据

背景
如果脏页面刷完了,内存还是不够,说明系统里有很多不再频繁访问的干净缓存(如刚解压完的安装包、读完没再用的配置文件)。

目的
把这些最近最少使用的缓存页直接扔掉。

机制

  • 内核维护LRU链表
  • 内存紧张时,从链表尾部开始回收。
  • 如果页面是干净的,直接回收;如果是脏的,先写回再回收(进入第一层)。

关键点

  • 这层不涉及磁盘写入(除非遇到脏页),只是丢掉缓存。
  • 这是成本最低的内存回收方式。

第三层:使用交换分区 —— 内存“换出”

背景
缓存清得差不多了,内存还是不够。但进程们确实占用了大量匿名页(如堆、栈、malloc分配的内存),这些页面没有对应的磁盘文件

目的
把不常用的进程内存页偷出来,暂时存到磁盘的swap分区。

代价

  • 极慢。内存纳秒级,磁盘毫秒级,差了百万倍。
  • 频繁swap = 系统变卡,磁盘I/O打满。

控制参数

  • vm.swappiness(0–100):
  • 并不是“swap开关”,而是倾向于使用swap的程度
  • 值越高,内核越愿意把匿名页换出,而不是去回收文件缓存。
  • 值越低(如0),内核会尽可能先清缓存,直到万不得已才swap。

第四层:OOM Killer —— 保命机制

背景
缓存清了,swap也用了,内存还是不够(比如某个进程疯狂malloc)。再这样下去系统会彻底卡死,连sshd都无法响应。

目的
杀死一个进程,立刻释放它的全部内存

机制

  • 每个进程有一个oom_score
  • 占用内存越多,分数越高。
  • 运行时间越短,分数越高。
  • root权限进程、核心服务有保护。
  • OOM Killer选择分数最高的进程杀掉。
  • 日志会记录在dmesg/var/log/messages

关键点

  • 这是最后手段。走到这一步意味着系统已经濒临崩溃
  • 如果被杀的进程是关键服务(如数据库),后果是业务中断。

整体逻辑总结

层级动作代价典型场景
1加速刷脏页磁盘写入脏页比例超标
2丢弃缓存页几乎无(下次读盘)文件缓存占满
3换出到swap极高(磁盘I/O)物理内存不足
4杀进程业务中断内存完全耗尽

一句话概括
先刷脏、再丢缓存、再换出、最后杀人。


常见调优思路

  1. 如果磁盘极慢(如机械盘)
  • 调低vm.dirty_ratio(如5%),避免一次刷太多导致卡顿。
  • 调低vm.swappiness(如10),尽量不碰swap。
  1. 如果内存极贵(如云服务器)
  • 开swap(但注意磁盘IOPS限额)。
  • 适当调高vm.vfs_cache_pressure,让内核更积极回收dentry/inode缓存。
  1. 如果业务要求低延迟
  • 关闭swap(swapoff)。
  • 内存预留(vm.min_free_kbytes)。

这四层是Linux内核内存管理的标准动作,理解了它们,就能看懂freetopvmstat里很多指标的含义。

压力测试

yum install -y epel-release
yum install -y stress stress-ng

# 申请 2GB 内存,持续 60 秒
stress --vm 1 --vm-bytes 2G --timeout 60s

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注