一、什么时候需要内存调优?

  • 系统频繁 OOM(Out of Memory)杀进程
  • 响应变慢,free 显示内存耗尽,swap 使用率高
  • 数据库或 Java 应用 GC 频繁、停顿长
  • 你发现系统 cache 占了几十 G,想知道能不能回收

读完这篇你会:看懂 /proc/meminfo,设置合理的 swap 和缓存回收策略,定位内存泄漏,用大页提升性能。

前置条件:能登录 Linux 服务器,有 root 或 sudo。我用 Ubuntu 22.04 和 CentOS 7 验证过。


二、先把内存指标看懂,别被 free 骗了

很多新手只盯着 free 那一行,发现只剩几百兆,以为内存不够。其实 Linux 会尽量用空闲内存做 cache,cache 是可以回收的。

正确的看法:看 available 列。

free -h

示例输出(32G 内存机器):

              total        used        free      shared  buff/cache   available
Mem:           31G         12G        1.2G        123M         18G         18G
Swap:         2.0G        200M        1.8G

这里 free 只有 1.2G,但 available 有 18G,说明还有大量 cache 可以回收,系统并不紧张。used 12G 包含了进程实际占用 + cache,但 available 已经剔除了可回收部分。

详细看 /proc/meminfo

cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback"

重点字段:

  • MemAvailable:估计有多少内存可用于启动新应用(不触发 swap)
  • Buffers:块设备的缓冲区(比如磁盘元数据)
  • Cached:文件缓存(page cache)
  • Dirty:等待写回磁盘的脏页
  • Writeback:正在写回的页

三、swap 调优:别让它拖垮性能

3.1 swap 该设多大?

老教科书说 swap = 2 倍内存,那是内存 512M 时代的经验。现在服务器内存 64G、128G,你给 256G swap?浪费磁盘,还会让系统在内存压力下疯狂换页

我个人的经验:

  • 内存 < 8G:swap = 内存大小
  • 内存 8G - 64G:swap = 4G - 8G
  • 内存 > 64G:swap = 8G(或者干脆不要,除非跑 Hadoop 等需要 swap 的应用)

3.2 vm.swappiness:什么时候开始用 swap

范围 0-100,默认 60。值越大,越积极用 swap。

对数据库或延迟敏感应用,建议调到 10-20,让它尽量留在内存。

# 临时生效
sysctl -w vm.swappiness=10

# 永久生效
echo "vm.swappiness=10" >> /etc/sysctl.conf

注意:设成 0 不代表禁用 swap,只是极度不愿意换出。内核 3.5 以后,0 的含义略有变化,想彻底禁用 swap 可以用 swapoff -a

3.3 添加 swap 文件(不用分区)

如果当初没分 swap 分区,可以用文件临时顶:

dd if=/dev/zero of=/swapfile bs=1M count=4096   # 4G
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab

dd 会先写满 4G 零,会占用磁盘 IO。生产环境建议在低峰期操作。


四、缓存回收:什么时候手动 drop cache

Linux 会自动回收 cache,但有时候你想立即释放(比如测试内存泄漏)。不要在生产环境随便 drop,会导致性能抖动。

# 清空 page cache(文件缓存)
echo 1 > /proc/sys/vm/drop_caches

# 清空 dentry 和 inode 缓存
echo 2 > /proc/sys/vm/drop_caches

# 清空全部
echo 3 > /proc/sys/vm/drop_caches

我推荐:只在测试环境或明确知道 cache 占用了大量内存且业务低峰期时用 echo 1echo 3 会让系统短暂“失忆”,性能骤降。

另外,调 vm.vfs_cache_pressure(默认 100)可以控制内核回收目录/ inode 缓存的倾向。数值越大越积极回收。如果文件系统有很多小文件,可以调到 200,避免 cache 占满内存。

sysctl -w vm.vfs_cache_pressure=200

五、定位内存泄漏:哪个进程在偷吃?

5.1 按 RSS 排序

ps aux --sort=-%mem | head -20

记下可疑 PID。然后持续观察它的内存变化:

watch -n 2 "ps -p PID -o rss,vsz,comm"

如果 RSS 每几分钟涨 100M 且从不下降,基本是泄漏。

5.2 查看进程内存映射详情

cat /proc/PID/smaps | grep -E "Pss|Rss|Size" | head -20

Pss(比例驻留大小)是更准确的内存占用(考虑了共享库)。如果某个进程的 Pss 持续增长,基本确定泄漏

5.3 用 valgrind 找泄漏(开发环境)

生产环境别用 valgrind,开销太大。开发环境可以:

valgrind --leak-check=full ./your_program

彩蛋:用 pmap -x PID 快速看内存分布,pmap 的输出比 /proc/smaps 更直观。


六、大页内存(HugePages):数据库性能神器

TLB(页表缓存)条目有限,用默认 4K 页,访问大量内存时 TLB miss 频繁。2MB 或 1GB 的大页能显著提升性能(尤其是数据库、DPDK、JVM)。

6.1 配置 HugePages(2MB)

# 查看当前大页配置
cat /proc/sys/vm/nr_hugepages

# 临时分配 1024 个 2MB 大页(共 2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# 永久:在 /etc/sysctl.conf 添加
vm.nr_hugepages=1024

挂载 hugetlbfs

mkdir /mnt/huge
mount -t hugetlbfs none /mnt/huge
echo "none /mnt/huge hugetlbfs defaults 0 0" >> /etc/fstab

6.2 透明大页(THP):建议关闭

THP 是内核自动将内存页合并成大页,但容易导致延迟抖动。数据库(MySQL、PostgreSQL、MongoDB)官方都推荐关闭 THP

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

永久关闭:在 /etc/rc.local 或 systemd service 里加。


七、实战案例:MySQL 内存被 swap 拖死

场景还原

一台 MySQL 服务器,内存 64G,innodb_buffer_pool_size 设了 40G,系统 swap 8G。跑着跑着,MySQL 响应从 1ms 飙到 200ms。

free -h 看到 swap 用了 6G,available 还有 20G。明明有空闲内存,为什么用 swap?

排查

检查 vm.swappiness 默认 60。MySQL 进程内存占用虽然没超物理,但内核认为某些匿名页(比如 InnoDB 的 old blocks)不活跃,换出到 swap。

解决

  1. 调低 vm.swappiness=10
  2. 重启 MySQL 让内存重新分布
  3. 修改 MySQL 配置:innodb_numa_interleave=1(开启 NUMA 交错,避免内存分配不均)

结果:swap 使用降到了几十 MB,响应恢复正常。

教训:数据库服务器一定调低 swappiness,有条件的话用 numactl 绑定内存节点。


八、彩蛋:几个冷门但有用的内存命令

  • numactl --hardware 看 NUMA 拓扑。跨节点访问内存延迟高,可以用 numactl --cpunodebind=0 --membind=0 ./app 绑定。
  • slabtop 看内核 slab 缓存(比如 dentry、inode 占多少内存)。如果 dentry 太大,考虑调 vfs_cache_pressure
  • echo m > /proc/sysrq-trigger 会输出内存信息到内核日志(别随便敲,会触发 dump)。
  • smem 工具(需要安装)可以统计 PSS/USS,比 ps 更准确。

九、验证调优效果

  1. stress 模拟内存压力:
stress --vm 2 --vm-bytes 1G --timeout 60
  1. 同时观察 vmstat 1siso(swap in/out)。如果 si 持续大于 0,说明内存不足或 swappiness 太高。
  2. 对数据库,压测观察响应时间和 %iowait

十、常见问题(真有人问过我)

Q:free 显示 available 很多,但应用还是 OOM 被杀?
A:OOM 通常因为单个进程超过了 ulimit -v 或 cgroup 限制,不是系统总内存不足。检查 dmesg | grep -i oom 看哪个进程被杀。

Q:/proc/meminfo 里的 MemAvailable 是怎么算的?
A:估计值 = MemFree + Buffers + Cached - 不可回收部分(如共享内存)。不同内核版本算法有差异,但趋势可信。

Q:可以关闭 swap 吗?
A:可以。但强烈建议保留少量 swap,哪怕 1G,因为内核在某些内存压力下需要 swap 做后台回收,否则可能直接 OOM。云上很多实例默认无 swap,自己加文件。

Q:Java 应用 RSS 高但实际堆很小,为什么?
A:JVM 会预分配内存(Xmx),还会加载共享库、线程栈、Metaspace。用 jcmdNative Memory Tracking 分析。


最后总结

  • 先看 available,别被 free 吓到。
  • 调低 swappiness(数据库 10-20),避免无辜 swap。
  • 内存泄漏ps/proc/PID/smaps 跟踪 RSS 趋势。
  • 大页提升性能,关闭 THP 避免延迟毛刺。
  • NUMA 绑核对多路服务器很重要。

内存调优往往是被忽视的一环,很多人直接加内存完事。但优化 swap 和 cache 策略,有时能省下一台服务器。

你遇到过内存调优的奇葩问题吗?评论区分享一下,咱们一起分析。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐