VPS内存不足怎么解决?Linux内存查看、分析与优化教程

VPS 控制台显示内存使用率接近百分之百时,不一定代表马上会崩溃。Linux 会尽量把空闲内存用于文件缓存,以提高磁盘读取速度,这部分在应用需要时通常可以回收。真正的内存不足往往伴随可用内存持续降低、频繁交换、进程被 OOM Killer 终止或服务延迟明显上升。

这篇文章从 free、/proc/meminfo 和 vmstat 开始,逐步定位高占用进程、页缓存、Slab、共享内存和容器限制,并给出应急与长期优化方法。排查时不要先清缓存或重启,因为这些操作会破坏现场,而且不能解决泄漏、配置过大和资源限制等根因。

先理解 Linux 内存统计

Linux 内存不是简单分成“已用”和“未用”。应用的匿名内存、文件页缓存、内核 Slab、共享内存、页表和交换空间都在总量中。最常用的查看命令是:

free -h

典型输出包含 total、used、free、shared、buff/cache 和 available。新手最容易只盯 free,但更有参考价值的是 available。它估算在不发生交换的情况下,系统还能提供给新应用的内存,包含一部分可回收缓存。

字段 含义 判断要点
used 已使用内存的综合估算 包含多个类别,不能单独判断压力
free 完全未使用的内存 Linux 通常不会刻意保持很高
buff/cache 块设备缓存、文件页缓存和部分可回收内核缓存 较高可能是正常优化行为
available 估算可立即供应用使用的内存 持续很低才更值得警惕
Swap used 已换出的内存页 少量历史占用不等于正在频繁交换

“交换空间有使用”与“系统正在抖动”不是一回事。内核可能把很久未访问的页面留在 swap,即使当前内存已经恢复。真正需要关注的是短时间内持续发生的换入和换出,以及它是否伴随磁盘等待和响应变慢。

VPS 还可能受虚拟化或容器配额影响。系统里看到的总内存通常是实例配额,而宿主机是否存在压力需要结合平台监控。运行在容器中的应用即使宿主机有空闲,也可能因容器限制触发 OOM。因此要先确认问题发生在整台 VPS、某个服务,还是某个控制组内。

用 free、meminfo 和 vmstat 确认内存压力

先做一轮低开销快照:

date
uptime
free -h
swapon --show

然后查看更完整的内核统计:

cat /proc/meminfo

/proc/meminfo 中值得关注的字段包括:

  • MemAvailable:无需明显交换即可使用的估算内存。
  • Cached 与 Buffers:文件数据和块设备相关缓存。
  • AnonPages:应用匿名页,常见于堆、栈等进程内存。
  • Shmem:共享内存和 tmpfs 使用量。
  • Slab:内核对象缓存总量,其中 SReclaimable 通常可回收,SUnreclaim 较难回收。
  • PageTables:进程页表占用,进程或映射数量极大时可能升高。
  • SwapTotal 与 SwapFree:交换空间总量和剩余量。
  • Dirty 与 Writeback:待写回或正在写回磁盘的页面。

单次数据仍不够,需要用 vmstat 连续采样:

vmstat 1 10

重点看 free、buff、cache、si、so、r、b 和 wa。si、so 分别表示从 swap 换入和换出;它们持续出现并伴随 wa 升高、服务变慢,说明系统可能在内存与磁盘之间反复搬运页面,也就是常说的交换抖动。偶发非零值不应脱离业务表现单独判断。

安装了 sysstat 后,可以观察交换页活动:

sar -W 1 10

若命令不存在,可按发行版安装:

# RHEL、Rocky、CentOS
sudo dnf install -y sysstat

# Debian、Ubuntu
sudo apt update
sudo apt install -y sysstat

还要确认 tmpfs 是否被写满。/tmp、/run、/dev/shm 可能使用内存作为存储:

df -h -t tmpfs -t devtmpfs
findmnt -t tmpfs

如果应用把大文件写入 /dev/shm 或其他 tmpfs,内存会被文件占用,但普通进程排名未必直观显示。先定位文件和用途,不要直接删除仍被程序使用的数据。

用 top、ps、smem 定位高占用进程

先在 top 中按内存排序:

top

进入后按 M 按内存排序,关注 RES、VIRT、SHR 和进程运行时间。VIRT 是虚拟地址空间,不等于实际占用;数据库和 Java 可能预留很大的虚拟空间。RES 更接近当前驻留在物理内存中的页面,但共享库页面可能在多个进程中重复统计。

使用 ps 保存文本快照:

ps -eo pid,ppid,user,comm,rss,vsz,%mem,etime,args --sort=-rss | head -n 25

rss 与 vsz 默认以 KiB 为单位。比较多个工作进程时,简单相加 RSS 可能高估共享页占用。更精确地分析比例共享集,可以安装 smem:

# Debian、Ubuntu
sudo apt update
sudo apt install -y smem

# RHEL、Rocky、CentOS;软件包可能需要扩展仓库
sudo dnf install -y smem

查看按 PSS 统计的进程内存:

sudo smem -tk
sudo smem -r -k -s pss

PSS 会把共享页面按共享进程数量分摊,适合评估多进程服务器的真实贡献。USS 表示进程独占页,RSS 则包含共享页。工具口径不同,排障记录中应注明使用的是哪一个指标。

已知 PID 后,可以查看映射汇总:

PID=1234
sudo pmap -x $PID | tail -n 20
cat /proc/$PID/status
cat /proc/$PID/smaps_rollup

smaps_rollup 在支持的内核上提供该进程映射的汇总信息,其中 Rss、Pss、Private_Dirty 和 Swap 很有价值。读取其他用户进程通常需要 root 权限。把 1234 换成真实 PID,并记录采样时间。

判断泄漏不能只看某个时刻占用大。数据库缓存主动使用大量内存可能完全正常,真正可疑的是在请求量和数据量稳定时,进程 PSS 或私有脏页持续单调增长,业务空闲后也不回落。可以每隔固定时间采样并与发布版本、流量和任务记录对比。

若发现服务反复消失,应检查是否被 OOM Killer 终止:

sudo journalctl -k --since '-24 hours' --no-pager | grep -i -E 'out of memory|killed process|oom'
sudo dmesg -T | grep -i -E 'out of memory|killed process|oom'

日志会给出被杀进程、内存状态和 OOM 发生位置。不要只看到某进程被杀就断定它是泄漏源,内核会根据评分选择牺牲目标,被杀的可能只是当时评分较高的进程。还要查是谁把系统推到了极限。

分析页缓存、Slab、应用和容器内存

页缓存高通常不是故障

Linux 用空闲内存缓存最近访问的文件,数据库读取、备份和大文件扫描后,buff/cache 可能显著上升。只要 MemAvailable 仍足够、没有持续交换且应用正常,就不需要清理。强行写入 drop_caches 会让后续读取变成磁盘 I/O,造成延迟尖峰,而且不会释放进程匿名内存,因此不应作为常规“优化”。

若怀疑某次大文件扫描挤压了业务缓存,应调整任务时间、读取方式和 I/O 优先级,而不是定时清缓存。长期解决方案可能是让备份在副本执行、限制并发、把任务迁到独立主机,或为数据库保留更合理的缓存与内存余量。

Slab 过高要定位具体内核对象

查看 Slab 概况:

sudo slabtop

也可以读取:

head -n 20 /proc/slabinfo

目录项和 inode 缓存较多时,可能与海量小文件、容器层或频繁文件扫描有关。SReclaimable 较高通常能被内核回收;SUnreclaim 持续增长则需要结合内核版本、驱动和工作负载排查。不要直接修改不理解的内核参数,应先在测试环境验证。

Java、MySQL、Redis 要看各自内存模型

Java 的最大堆只是总进程内存的一部分,还包括元空间、线程栈、直接内存、代码缓存和本地库。可在具备 JDK 工具且权限允许时查看:

jcmd 1234 GC.heap_info
jcmd 1234 VM.flags

Native Memory Tracking 必须在 JVM 启动时启用,启用后才能使用相应的 jcmd ... VM.native_memory summary,它会产生额外开销,不能在未评估时临时照搬生产配置。若频繁 Full GC,不要只扩大堆,应检查对象分配、缓存上限和线程数量。

MySQL 的 InnoDB 缓冲池会主动占用内存,此外每个活跃连接的排序、连接和临时缓冲可能叠加。最大连接数过大并不代表吞吐更高。Redis 数据集主要在内存中,生成 RDB 或 AOF 重写时的写时复制会增加峰值,内存规划必须留出余量。优化这类服务时要用它们自己的状态指标,而不是只按 RSS 判断异常。

容器限制可能早于整机耗尽

Docker 或编排环境中的容器可能设置内存上限。容器触及限制时会发生控制组 OOM,即使主机 free 仍显示有可用内存。先查看容器状态与用量:

docker stats --no-stream
docker inspect CONTAINER_NAME --format '{{.HostConfig.Memory}} {{.State.OOMKilled}}'

限制值为字节,零通常表示未设置 Docker 级限制。编排平台还要检查请求、限制、重启原因和节点压力。解决时应同时评估容器真实峰值与宿主机总容量,不能只提高单容器上限,否则可能把问题转移到整机。

应急止损与长期优化怎么做

当可用内存快速下降、交换持续发生并且服务超时时,先暂停非关键批处理、备份压缩和数据导入。若有负载均衡,可摘除异常节点,保留日志、free、vmstat、进程排名和 OOM 记录后再处理。不要在没有副本时随意终止数据库。

确认某服务异常后,优先通过服务管理器优雅重启:

sudo systemctl restart SERVICE_NAME

风险提示:重启会中断连接并丢失进程内状态,数据库和消息服务还可能执行恢复。操作前确认有健康副本、持久化完成和回滚方案。重启只能释放当次占用,若根因是泄漏或错误配置,问题会再次出现。

交换空间可以降低瞬时 OOM 风险,但不能弥补长期不足。先检查:

swapon --show
free -h

如果实例没有 swap、磁盘有余量且业务允许较慢的应急换页,可以在维护窗口增加交换文件。创建前确认磁盘空间,并限制权限:

df -h /
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo sh -c 'grep -q "^/swapfile " /etc/fstab || echo "/swapfile none swap sw 0 0" >> /etc/fstab'

低延迟数据库一旦频繁使用 swap,性能可能显著恶化。若 fallocate 不适用于当前文件系统,应查看发行版建议。回滚前必须保证物理内存能容纳已换出页面,再执行 swapoff;内存不足时强制关闭 swap 可能触发 OOM。

长期优化应按根因进行:

  1. 限制无边界缓存和队列:给应用内缓存、请求队列、消息批次和上传大小设置上限。
  2. 修复内存泄漏:使用运行时分析器比较对象或分配趋势,在接近生产的数据量下复现。
  3. 调整进程模型:PHP-FPM、Gunicorn 等工作进程数应按单进程峰值内存计算,不能只按 CPU 核心数增加。
  4. 控制数据库配置:合理设置缓冲池、连接数与每连接缓冲,避免理论最大值总和远超物理内存。
  5. 设置容器请求与限制:为关键容器预留容量,同时避免单容器无限增长。
  6. 拆分服务或升配:应用与数据库互相挤压时可拆机;持续正常负载超过容量时再增加内存。

可以根据“单进程峰值乘以并发进程数,再加系统、缓存和守护进程余量”估算容量。例如,一个工作进程峰值二百兆字节,运行二十个就可能接近四吉字节,还未计算共享页、数据库和系统开销。实际值应通过 PSS 和压测修正。

建立 MemAvailable、swap 换入换出、容器 OOM、进程 PSS 与重启次数告警。告警要设置持续时间,避免页缓存变化造成误报。每次发布记录版本标记,才能快速判断内存增长是否与新代码相关。

常见问题

free 很低,是否应该立即加内存?

不一定。先看 available、交换活动和业务延迟。大量可回收页缓存会让 free 很低,但系统仍可能健康。只有持续真实压力或容量趋势不足时才需要优化或升配。

可以定时执行 drop_caches 吗?

不建议。它会清掉有价值的文件缓存,造成后续磁盘读取变慢,也不能释放应用泄漏的匿名内存。应定位是哪类内存增长并处理根因。

swap 使用了一点是否意味着内存不足?

不一定。少量长期不变化的 swap 可能只是历史冷页。若 vmstat 的 si、so 持续活跃并伴随延迟和磁盘等待,才说明交换正在影响性能。

RSS、PSS 和 VIRT 应该看哪个?

VIRT 是虚拟地址空间,常明显大于实际占用;RSS 包含共享页,多个进程相加可能重复;PSS 按比例分摊共享页,更适合比较进程对物理内存的贡献。

内存不足时重启是否有效?

重启能释放当次占用,适合作为有计划的应急止损,但不能修复泄漏、缓存无上限或配置过大。重启前应保存数据与现场,并准备服务接管。

总结

  • Linux 会用空闲内存做缓存,判断压力应优先看 MemAvailable、持续交换、OOM 和业务延迟,而不是只看 free。
  • 使用 free、/proc/meminfo、vmstat 确认整体压力,再用 top、ps、smem 和 smaps_rollup 定位进程贡献。
  • 页缓存、Slab、Java 堆外、数据库缓存和容器限制的口径不同,必须按各自机制分析。
  • 不要把清缓存、强杀进程或重启当作常规优化;应先保留现场,并评估对连接、数据和恢复时间的影响。
  • 长期通过限制缓存与并发、修复泄漏、调整进程模型、设置容器配额和容量告警解决问题,正常负载确实超限时再升配或拆分服务。