服务器内存告警灯亮起,应用响应变慢甚至直接宕机,这是运维人员最不愿意看到的场景。遇到内存占用过高,手忙脚乱地重启服务治标不治本,第一时间应该通过命令行工具精准定位是哪个进程在消耗内存,以及缓存是否占用了过多资源。下面直接进入排查的核心步骤,不讲虚的。
快速概览整体内存状况登录服务器后,不要急着敲复杂命令,先用 free -h 看一眼内存全貌。这条命令输出直观,-h 参数让数字以人类可读的 GB 或 MB 显示。输出中需要重点关注 available 列,它表示系统还能分配给新进程的内存,比 free 列更真实,因为 free 不包括可回收的缓存。如果 available 值很小,说明内存确实紧张。同时注意 swap 行,如果 used 值很高,说明物理内存不够用了,系统频繁把数据换入换出到磁盘,性能会急剧下降。这个初步判断决定了后续排查的方向:是某个进程内存泄漏,还是缓存把内存吃光了。
精准定位占用内存的进程确定了内存确实紧张后,用 top 或 htop 命令实时查看进程资源占用。top 启动后按大写 M 键,进程会按内存使用率从高到低排序,一目了然。重点关注 RES 列,它表示进程实际占用的物理内存,不包括交换出去的部分。VIRT 列是虚拟内存大小,包含了未实际分配的地址空间,参考价值不大。%MEM 列显示进程占用物理内存的百分比,结合 RES 可以快速锁定罪魁祸首。如果发现某个进程 RES 值持续增长且不回落,大概率存在内存泄漏,需要进一步分析该进程的堆内存或联系开发人员排查代码。
如果想一次性输出快照而不进入交互界面,用 ps 命令更合适。执行 ps aux --sort=-%mem | head -20 可以列出内存占用最高的前 20 个进程。输出中 RSS 列就是实际物理内存占用,单位是 KB。这条命令适合写进监控脚本,定时采集进程内存快照,便于事后回溯分析。如果怀疑是 Java 应用内存过高,可以用 jps -v 找到 Java 进程 PID,再用 jstat -gcutil [PID] 1000 查看堆内存各区域使用率,重点关注老年代使用比例是否持续接近 100%,这通常是内存泄漏的典型特征。
深入分析系统缓存占用很多时候 free -h 显示 free 内存很少,但 available 还比较充裕,这种情况通常是因为 Linux 把空闲内存用作了文件系统缓存,用来加速文件读写。这本身是正常行为,内核会在进程需要内存时自动回收缓存。但如果 cached 值异常巨大,甚至导致 swap 使用,就需要手动干预。通过 cat /proc/meminfo 可以查看更详细的内存分类,其中 Cached 表示页缓存大小,Buffers 是块设备缓存,SReclaimable 是可回收的内核 slab 缓存。这三者加起来大致等于 free 命令中 buff/cache 的值。如果 SReclaimable 特别大,可能是目录项缓存或 inode 缓存占用过多,通常发生在大量小文件被访问后。
要具体查看哪些文件占用了页缓存,可以用 fincore 工具,或者更通用的 vmtouch 命令。vmtouch 可以显示指定目录或文件在缓存中的大小,比如 vmtouch /var/log 就能看到日志文件占用了多少缓存。如果发现某些大文件或者已经删除但缓存未释放的文件占着内存,可以通过清空特定文件缓存或重启相关服务来回收。对于 slab 缓存,用 slabtop 命令可以实时查看内核对象缓存的占用情况,按 c 键按缓存大小排序,如果 dentry 项特别大,说明目录缓存膨胀,可以执行 sync && echo 2 > /proc/sys/vm/drop_caches 来释放可回收的目录项和 inode 缓存。注意这个操作会瞬间清空缓存,短期内文件读取性能会下降,生产环境执行前务必评估影响。
排查内存泄漏的进阶手段如果通过 top 或 ps 锁定了某个进程内存持续增长,但无法直接判断是代码逻辑问题还是正常业务增长,可以用 pmap 命令查看该进程的内存映射详情。执行 pmap -x [PID] 会列出进程的每一段内存映射区域,包括堆、栈、匿名映射、文件映射等,以及各自的 RSS 和 Dirty 页大小。重点关注匿名映射部分,如果某段地址空间 RSS 很大且持续增长,说明进程在动态分配内存后没有释放。结合 strace 跟踪进程的系统调用,可以看到内存分配相关的 mmap、brk 等调用记录,帮助开发人员定位到具体的代码路径。
对于 Python 应用,可以用 tracemalloc 模块在代码中埋点,或者用 py-spy 工具在不重启进程的情况下获取内存快照。Node.js 应用可以用 node --inspect 配合 Chrome DevTools 分析堆快照。这些语言级工具能精确到对象级别的内存占用,比操作系统级命令更深入。如果进程是数据库服务比如 MySQL 或 PostgreSQL,内存占用高可能是正常的缓冲池配置导致,需要检查配置文件中的 innodb_buffer_pool_size 或 shared_buffers 参数,而不是盲目认为内存泄漏。
缓存与内存回收机制详解理解 Linux 内核的内存回收机制,有助于判断什么时候该手动干预。内核将内存分为匿名页和文件页,匿名页是进程动态分配的内存,文件页是文件系统缓存。当内存紧张时,内核会优先回收文件页,因为文件页有磁盘备份,回收后需要时重新读取即可。匿名页没有磁盘备份,回收前必须先写入 swap 分区。如果系统 swap 使用率持续上升,说明匿名页压力很大,文件缓存已经不足以腾出空间。此时应该检查是否有进程匿名内存占用过高,而不是一味清理缓存。通过 cat /proc/meminfo 查看 Active(file) 和 Inactive(file) 可以了解文件缓存的状态,Inactive(file) 是回收的首选目标,如果这个值很小,说明可回收的缓存已经不多,问题根源在匿名内存。
vmstat 1 命令可以实时观察内存回收行为,si 和 so 列表示每秒换入和换出的内存量,如果这两个值持续非零,说明系统在频繁交换,性能已经严重受影响。bi 和 bo 列是块设备读写,如果数值很高,可能是缓存回收导致的大量磁盘 IO。通过 vmstat 的输出节奏,能判断内存压力是突发性还是持续性,突发性通常与定时任务或流量峰值有关,持续性则几乎可以确定存在内存泄漏或配置不当。
常见场景与应对策略场景一:Web 服务器内存高。先检查是不是 PHP-FPM 或 Apache 的进程数过多,每个进程独立占用内存,并发量高时内存消耗线性增长。用 ps aux | grep php-fpm | wc -l 查看进程数,结合 pm.max_children 配置评估是否需要调低。场景二:Redis 或 Memcached 内存高。这些缓存服务本身就会尽量使用内存,需要检查 maxmemory 配置是否合理,以及是否存在大量过期但未淘汰的 key。用 redis-cli info memory 查看内存碎片率,如果碎片率过高,可能需要重启实例或执行 MEMORY PURGE。场景三:日志文件被删除但空间未释放。有时候运维删除了大日志文件,但进程还在向文件写数据,导致磁盘空间释放了但内存中的缓存还在。用 lsof | grep deleted 可以找到这些幽灵文件,重启相关进程即可释放内存。
场景四:容器化环境中的内存限制。如果服务器运行 Docker 容器,宿主机看到的内存高可能是容器内进程消耗导致。用 docker stats 查看各容器内存使用量,再进入容器内部用 top 或 ps 排查。容器有 cgroup 内存限制,如果容器内进程超过限制会被 OOM Killer 杀掉,宿主机内存反而可能看起来正常。因此排查时要分层进行,先确认是宿主机问题还是容器问题。
预防性监控与自动化排查完当前问题后,建立预防机制才能避免半夜被报警叫醒。在系统层面,用 sar -r 命令可以查看历史内存使用情况,如果服务器安装了 sysstat 包,系统会每十分钟自动采集一次内存数据,通过 sar -r -f /var/log/sa/saXX 可以回溯任意时间点的内存状态。进程层面,可以写一个简单的脚本,每分钟采集 ps aux 中 RSS 最大的进程并记录到日志,配合 awk 和 sort 做趋势分析。当某个进程内存使用超过阈值时,自动触发 pmap 或 jstat 采集详细信息,为事后分析保留现场。这些数据比出问题后再手动敲命令有价值得多。
内存告警阈值设置也需要合理,不要一看到 free 少就报警。应该基于 available 内存或 swap 使用率来设置告警,available 低于物理内存的 10% 且 swap 使用超过 20% 时,才触发严重告警。这样可以避免缓存正常波动导致的误报,让告警真正指向需要人工介入的问题。
