Ubuntu服务器内存占用过高时,先别急着重启或加内存。多数情况下,内存被“吃掉”是缓存、日志进程或某个失控服务造成的,而不是物理内存真的不够。先执行 free -h 看一眼真实的内存分布。如果 used 很高但 buff/cache 也很高,说明内存主要被系统缓存占用,这属于正常现象,应用需要时内核会自动回收。真正要警惕的是 available 很低、swap 使用持续增长,或者某个进程的 RSS 异常膨胀。

第一步:用 top 和 ps 定位占用内存最大的进程

执行 top 后按大写 M,可以按内存使用率排序。重点看 %MEM 和 RES 两列。RES 是进程实际占用的物理内存,单位默认是 KiB。如果发现某个进程 RES 达到几个 GB,而且不是数据库或 Java 这类预期会吃内存的服务,基本就能锁定问题。更精确的命令是:

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

这条命令会列出内存占用最高的前 20 个进程。注意 RSS 列,它表示进程独占的物理内存。如果看到多个 php-fpm、nginx worker 或 mysql 线程占用异常,就要进一步分析是不是配置问题。

常见元凶一:MySQL/PostgreSQL 缓冲池配置过大

很多 Ubuntu 服务器内存占用过高,根源是数据库把大量内存分配给了缓冲池。MySQL 的 innodb_buffer_pool_size 默认可能设置为物理内存的 70% 甚至更高。如果这台服务器还同时跑着 Web 服务和其他应用,内存就会非常紧张。查看当前配置:

mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

如果数值接近或超过物理内存的一半,而且服务器上还有其他服务,建议调低。例如 8GB 内存的服务器,如果只跑一个中等规模的网站,innodb_buffer_pool_size 设置为 2G 到 4G 通常足够。PostgreSQL 类似,重点看 shared_buffers 和 effective_cache_size。shared_buffers 一般设置为物理内存的 25% 左右即可,不要盲目调大。

常见元凶二:PHP-FPM 进程数过多

PHP-FPM 是 Ubuntu Web 服务器上最容易导致内存飙升的服务之一。默认的 pm.max_children 可能设置得比较高,每个 PHP-FPM 子进程会占用几十到几百 MB 内存。如果同时有几十个进程存活,内存很快就被耗尽。查看当前 PHP-FPM 进程数和内存占用:

ps -C php-fpm -o rss,cmd --no-headers | awk '{sum+=$1; count++} END {print "进程数:", count, "总内存MB:", sum/1024}'

然后检查配置文件,通常在 /etc/php/8.1/fpm/pool.d/www.conf 或类似路径。重点看这几个参数:

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500

如果服务器内存只有 4GB,max_children 设置 50 就太激进了。一个简单算法:用单个 PHP-FPM 进程平均内存占用去除可用内存,再留出 20% 余量。例如单个进程 80MB,可用内存 3GB,那么 max_children 设置在 30 左右比较合理。同时设置 pm.max_requests 可以让进程定期重启,释放累积的内存碎片。

常见元凶三:Java 应用堆内存设置不当

Java 应用的内存问题非常典型。很多运维看到服务器内存占用高,其实是 JVM 的堆内存和元空间配置不合理。Java 进程启动时如果没有显式指定 -Xmx,默认会使用物理内存的 1/4,这在某些场景下会非常大。更麻烦的是,JVM 除了堆内存,还有 Metaspace、线程栈、JIT 编译缓存等非堆内存,实际占用往往比 -Xmx 指定的还要高出 30% 到 50%。

排查 Java 进程内存:

jps -lv
jmap -heap [pid] 2>/dev/null | head -30

如果发现 -Xmx 设置得过大,或者根本没有设置,就需要调整启动参数。例如原来 -Xmx4g,但服务器总共只有 8GB 内存,还要跑 Nginx 和 MySQL,就明显不合理。建议根据应用实际负载,用 jstat -gcutil [pid] 2000 观察 GC 情况,再决定合适的堆大小。不要只盯着堆内存,还要用 pmap -x [pid] 查看进程的 RSS 和虚拟内存分布,确认是否有 Native Memory 泄漏。

用 smem 查看 PSS 和 USS,避免被共享内存误导

ps 和 top 里的 RSS 有一个问题:它把共享内存也计算进去了。如果多个进程共享同一块内存,RSS 会重复计算,导致看起来内存占用很高,但实际可用内存并不紧张。安装 smem 可以更准确地查看进程独占内存:

sudo apt install smem
smem -rs pss

PSS 是进程按比例分摊共享内存后的结果,USS 是进程独占的内存。如果发现某个进程 RSS 很高但 PSS 很低,说明它大量使用了共享库,实际压力没那么大。这个工具在排查多进程 Web 服务时特别有用。

检查内存泄漏:用 valgrind 和 pmap 定位增长点

如果某个进程的内存占用持续增长、不释放,可能是内存泄漏。对于 C/C++ 程序,可以用 valgrind 做检测,但生产环境直接跑 valgrind 会严重影响性能。更实际的做法是定期记录进程的 RSS 和 /proc/[pid]/smaps 中的匿名内存,观察增长趋势。例如:

while true; do date; grep -E "^(Rss|Anonymous)" /proc/[pid]/status; sleep 60; done

对于 Python 应用,可以用 tracemalloc 模块追踪内存分配。对于 Node.js,可以用 --inspect 配合 Chrome DevTools 查看堆快照。关键是先确认是泄漏还是正常增长,再决定是升级版本、修复代码还是定期重启进程。

系统级内存回收参数调优

Ubuntu 默认的内存回收策略有时会显得“过于保守”,导致 available 内存偏低。可以通过调整 vm.swappiness 和 vfs_cache_pressure 来改善。vm.swappiness 控制内核将匿名页交换到 swap 的倾向,默认 60。对于内存紧张的服务器,可以适当调低,让内核优先回收页缓存而不是把进程内存换出:

sudo sysctl vm.swappiness=10
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf

vfs_cache_pressure 控制内核回收 dentry 和 inode 缓存的积极性。默认 100,如果服务器上文件操作频繁,可以调高到 200 或更高,让内核更积极地回收文件系统缓存:

sudo sysctl vm.vfs_cache_pressure=200
echo "vm.vfs_cache_pressure=200" | sudo tee -a /etc/sysctl.conf

但要注意,这些参数不是万能的。如果应用本身就需要大量内存,调这些参数只是让系统表现得更“正常”,并不能解决根本问题。

检查 tmpfs 和 /dev/shm 的占用

很多人忽略了一点:/dev/shm 是挂载在内存上的 tmpfs,如果里面有大量文件,也会占用物理内存。执行 df -h /dev/shm 查看使用情况。如果发现占用很高,检查里面是否有残留的共享内存文件、临时上传文件或者某些应用的缓存。清理不需要的文件可以立即释放内存:

ls -lah /dev/shm
rm -f /dev/shm/不需要的文件

同样,/tmp 如果挂载为 tmpfs,也可能占用内存。可以用 mount | grep tmpfs 查看所有 tmpfs 挂载点。

用 systemd-cgtop 查看 cgroup 级别的内存占用

如果服务器上跑着多个 systemd 服务,用 systemd-cgtop 可以按服务分组查看内存、CPU 和 IO 占用。这个工具比单纯看进程更直观,能快速定位是哪个服务整体消耗了大量内存:

systemd-cgtop

输出会按 cgroup 分组显示内存占用,比如 nginx.service、mysql.service、docker.service 等。如果某个服务的 cgroup 内存占用异常高,再进入该 cgroup 查看具体进程,效率更高。

容器环境下的内存排查

如果 Ubuntu 服务器上运行着 Docker 容器,内存占用过高的问题可能出在容器内部。宿主机上看到的 RSS 可能包含多个容器的进程,不容易区分。用 docker stats 查看每个容器的内存使用量:

docker stats --no-stream

如果某个容器内存使用接近或超过限制,容器内的进程可能会被 OOM Killer 杀掉。检查容器日志和 dmesg 输出,确认是否有 OOM 记录:

dmesg -T | grep -i "out of memory"

对于容器化应用,建议在 docker run 或 docker-compose 中显式设置内存限制,避免单个容器无节制地消耗宿主机内存。同时注意,容器内的 free 命令显示的是宿主机的内存信息,不要被误导。

OOM Killer 的日志分析

当内存真的耗尽时,内核会触发 OOM Killer 杀掉进程。很多人只看到某个进程突然消失,却不知道原因。检查 /var/log/syslog 或 /var/log/kern.log,搜索 “Out of memory” 或 “Killed process”:

grep -i -E "out of memory|killed process" /var/log/syslog /var/log/kern.log

日志中会明确写出被杀进程的名字、PID 以及触发 OOM 时的内存分布。如果发现被杀的是 MySQL 或 Nginx,说明内存压力已经非常严重,必须从源头解决,而不是简单重启了事。

长期监控和预警

排查完当前问题后,建议部署轻量级监控,避免再次出现内存告急时手忙脚乱。可以用 Prometheus + node_exporter 采集内存指标,配合 Grafana 展示。如果不想搭建整套监控,至少用 cron 定期执行 free -h 和 ps 输出到日志文件,方便事后回溯。例如每小时记录一次:

0 * * * * echo "$(date) $(free -h | grep Mem)" >> /var/log/mem_monitor.log

更主动的做法是设置内存使用率告警。当 available 内存低于物理内存的 10% 时,自动发送邮件或触发 Webhook。这样在问题恶化前就能介入处理。

Ubuntu 服务器内存占用过高不是单一原因造成的,需要结合进程、服务配置、内核参数和容器环境综合分析。先定位进程,再判断是配置问题、泄漏还是正常缓存,最后针对性调整。盲目重启或加内存只能暂时掩盖问题,找到根因才是关键。