Linux内存占用过高排查手册:OOM Killer、内存泄漏与Swap优化实录

服务器内存耗尽是比磁盘告急更危险的故障——轻则进程被系统强制杀死,重则服务器完全失去响应。本文从诊断工具到根治方案,系统梳理Linux内存问题的完整处置流程。

一、快速查看内存状态

# 最直观的内存概况
free -h

# 输出解读:
#               total    used    free   shared  buff/cache   available
# Mem:           3.8G    2.9G    120M    45M       780M        680M
# Swap:          2.0G    1.2G    800M
#
# available(可用)才是真正可以分配给新进程的内存
# buff/cache是内核缓存,压力大时会自动释放
# 实时动态监控(每2秒刷新)
watch -n 2 free -h

# 查看内存使用详细分项
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Cached|SwapTotal|SwapFree"

二、定位内存占用进程

# 按内存使用量排序(%MEM列)
ps aux --sort=-%mem | head -20

# top命令中按M键按内存排序
top
# 进入top后按 M 键即可按内存降序排列

# 更详细的进程内存分析(安装smem)
apt install smem -y
smem -s rss -r | head -20  # 按RSS物理内存排序

各内存指标含义

指标全称含义参考意义
VSZVirtual Size进程申请的虚拟内存通常偏大,参考价值低
RSSResident Set Size进程实际占用的物理内存最重要的参考指标
PSSProportional Set Size含共享库按比例分摊的内存多进程系统更准确

三、OOM Killer:进程被系统杀死的分析

当内存耗尽时,Linux内核的OOM Killer会自动选择并杀死某个进程以回收内存,被杀的进程会在日志中留下记录。

查看OOM Killer日志

# 查看最近的OOM事件
dmesg | grep -i "oom\|killed process\|out of memory" | tail -30

# 或从系统日志查找
grep -i "oom\|killed" /var/log/syslog | tail -30
journalctl -k | grep -i "oom" | tail -30

典型OOM日志输出解读

# 示例OOM日志
# [123456.789] Out of memory: Kill process 12345 (php-fpm) score 850 or sacrifice child
# [123456.790] Killed process 12345 (php-fpm) total-vm:2048000kB, anon-rss:1536000kB

# score值越高越容易被杀,评分依据:进程内存占用 / 总内存
# 可以通过调整oom_score_adj降低关键进程被杀概率

保护关键进程不被OOM Killer杀死

# 为MySQL设置最低OOM优先级(-1000表示永不被杀)
echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj

# 永久生效(在systemd服务配置中)
# /etc/systemd/system/mysql.service.d/override.conf
[Service]
OOMScoreAdjust=-1000

四、内存泄漏的识别与定位

内存泄漏的典型特征:进程内存使用量随时间持续增长,重启后恢复正常,运行一段时间后再次增长。

监控特定进程的内存增长趋势

<code">#!/bin/bash
# 每分钟记录一次指定进程的内存使用
PROCESS="php-fpm"
LOG="/var/log/mem-monitor.log"

while true; do
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    MEM=$(ps aux | grep "$PROCESS" | grep -v grep | awk '{sum+=$6} END {print sum/1024 " MB"}')
    echo "$TIMESTAMP - $PROCESS 内存: $MEM" >> "$LOG"
    sleep 60
done

PHP内存泄漏定位

<code"># 开启PHP慢请求日志(排查内存泄漏来源)
# php-fpm配置中添加:
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s

# 查看慢请求日志中的高频调用栈
cat /var/log/php-fpm-slow.log | grep "script_filename" | sort | uniq -c | sort -rn | head -20

五、常见高内存占用场景及解决方案

场景1:MySQL内存占用过高

# 查看MySQL内存配置
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# 调整到合理值(建议为总内存的50%~70%)
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
innodb_buffer_pool_size = 1G    # 2G内存服务器建议1G
innodb_buffer_pool_instances = 2
key_buffer_size = 64M

场景2:PHP-FPM子进程过多

# /etc/php/8.1/fpm/pool.d/www.conf
[www]
pm = dynamic
pm.max_children = 20        # 最大子进程数(根据内存调整)
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500       # 每个子进程最多处理500次请求后重启(防内存泄漏)

场景3:Node.js内存泄漏

# PM2配置内存超限自动重启
pm2 start app.js --max-memory-restart 500M

# 或在ecosystem.config.js中
{
  max_memory_restart: '500M'
}

六、Swap配置优化

Swap是物理内存的应急缓冲,配置合理可以防止OOM Killer乱杀进程,但过度依赖Swap会导致性能严重下降(磁盘I/O远慢于内存)。

创建Swap文件

# 创建2GB的Swap文件
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 开机自动挂载
echo "/swapfile swap swap defaults 0 0" >> /etc/fstab

# 验证
free -h

调整swappiness(关键参数)

# swappiness控制系统倾向于使用Swap的程度
# 0 = 尽量不使用Swap
# 100 = 积极使用Swap
# 默认值60,服务器建议设为10

sysctl vm.swappiness=10
echo "vm.swappiness=10" >> /etc/sysctl.conf

七、内存不足的根本解决方案

场景建议方案
单个进程内存泄漏定位泄漏代码并修复,临时设置定时重启
进程配置不合理调低PHP-FPM子进程数、MySQL缓冲池大小
业务增长内存不够升级VPS配置(IDC.Net支持在线升配)
多服务同一台机器将数据库迁移到独立服务器,分离资源

八、总结

Linux内存问题的排查顺序:free -h 确认状态 → ps aux --sort=-%mem 定位进程 → 查看OOM日志确认是否有进程被杀 → 根据具体进程调整配置。IDC.Net的香港VPS支持在线升级内存配置,业务增长时无需迁移数据,直接升配即可解决内存瓶颈。

THE END