内存不足/OOM问题
内存不足(Out of Memory, OOM)是Linux系统中严重的问题,会导致系统杀死进程、服务中断甚至系统崩溃。
🚨 问题现象
常见错误信息
# OOM Killer消息
Out of memory: Kill process
oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Killed process [PID] (process_name) total-vm:[memory]kB
# 内存分配失败
Cannot allocate memory
fork: retry: Resource temporarily unavailable
# 应用程序错误
java.lang.OutOfMemoryError
malloc: Cannot allocate memory
系统表现
- 系统响应极慢
- 进程被突然杀死
- 新进程无法启动
- 服务异常退出
- 系统频繁使用swap
- 登录困难或失败
🔍 问题诊断
1. 检查内存使用情况
# 查看内存使用状态
free -h
cat /proc/meminfo
# 查看内存使用详情
vmstat 1 5
sar -r 1 5
# 查看swap使用情况
swapon -s
cat /proc/swaps
2. 查看OOM日志
# 查看OOM事件
dmesg | grep -i "killed process"
grep -i "out of memory\|oom-killer" /var/log/messages
journalctl --since "1 hour ago" | grep -i oom
# 查看最近被杀死的进程
dmesg | grep "Killed process" | tail -10
3. 识别内存占用大户
# 按内存使用排序进程
ps aux --sort=-%mem | head -20
top -o %MEM
# 查看进程内存映射
pmap -x PID
cat /proc/PID/smaps
# 查看系统内存分布
cat /proc/meminfo | grep -E "(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree)"
4. 检查内存泄漏
# 监控进程内存使用趋势
while true; do
date
ps aux | grep process_name | grep -v grep
sleep 60
done > memory_trend.log
# 使用valgrind检查内存泄漏
valgrind --tool=memcheck --leak-check=full ./program
5. 分析OOM分数
# 查看进程OOM分数
for pid in $(ps -eo pid --no-headers); do
if [ -f /proc/$pid/oom_score ]; then
echo "PID: $pid, OOM Score: $(cat /proc/$pid/oom_score), Command: $(ps -p $pid -o comm --no-headers)"
fi
done | sort -k4 -nr | head -10
🛠️ 解决方案
立即释放内存
1. 清理系统缓存
# 同步磁盘缓存到硬盘
sync
# 清理页面缓存
echo 1 > /proc/sys/vm/drop_caches
# 清理目录项和inode缓存
echo 2 > /proc/sys/vm/drop_caches
# 清理所有缓存
echo 3 > /proc/sys/vm/drop_caches
# 重新启用缓存
echo 0 > /proc/sys/vm/drop_caches
2. 终止占用内存的进程
# 找出内存占用最大的进程
PID=$(ps aux --sort=-%mem | head -2 | tail -1 | awk '{print $2}')
echo "最大内存占用进程PID: $PID"
# 优雅终止进程
kill -TERM $PID
# 强制终止进程(如果优雅终止失败)
kill -KILL $PID
# 批量终止某类进程
pkill -f process_name
3. 重启服务释放内存
# 重启占用内存较多的服务
sudo systemctl restart mysql
sudo systemctl restart apache2
sudo systemctl restart nginx
# 重启后检查内存使用
free -h
调整OOM Killer行为
1. 配置OOM分数
# 查看进程的oom_score_adj值
cat /proc/PID/oom_score_adj
# 降低重要进程被杀死的概率
echo -1000 > /proc/PID/oom_score_adj # 完全禁用OOM killer
echo -500 > /proc/PID/oom_score_adj # 降低被杀概率
# 提高不重要进程被杀死的概率
echo 500 > /proc/PID/oom_score_adj
echo 1000 > /proc/PID/oom_score_adj # 最高优先级被杀
2. 系统级OOM配置
# 配置OOM处理策略
echo 2 > /proc/sys/vm/overcommit_memory # 不允许过量分配
echo 50 > /proc/sys/vm/overcommit_ratio # 过量分配比例
# 永久设置
echo "vm.overcommit_memory = 2" >> /etc/sysctl.conf
echo "vm.overcommit_ratio = 50" >> /etc/sysctl.conf
sysctl -p
3. 服务级别OOM保护
# 在systemd服务文件中设置OOM保护
sudo vim /etc/systemd/system/critical-service.service
[Unit]
Description=Critical Service
After=network.target
[Service]
Type=simple
ExecStart=/path/to/critical-service
OOMScoreAdjust=-1000 # 保护关键服务
MemoryMax=1G # 限制最大内存使用
[Install]
WantedBy=multi-user.target
# 重载并重启服务
sudo systemctl daemon-reload
sudo systemctl restart critical-service
内存优化配置
1. 调整swap策略
# 查看当前swappiness值
cat /proc/sys/vm/swappiness
# 临时调整swappiness
echo 10 > /proc/sys/vm/swappiness
# 永久设置
echo "vm.swappiness = 10" >> /etc/sysctl.conf
# 增加swap空间
sudo dd if=/dev/zero of=/swapfile bs=1G count=4
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 永久启用swap
echo "/swapfile none swap sw 0 0" >> /etc/fstab
2. 优化内存回收
# 配置内存回收参数
echo "vm.min_free_kbytes = 65536" >> /etc/sysctl.conf
echo "vm.vfs_cache_pressure = 50" >> /etc/sysctl.conf
echo "vm.dirty_ratio = 15" >> /etc/sysctl.conf
echo "vm.dirty_background_ratio = 5" >> /etc/sysctl.conf
# 应用配置
sysctl -p
🔧 内存监控脚本
OOM监控脚本
#!/bin/bash
# oom_monitor.sh
LOG_FILE="/var/log/oom_monitor.log"
EMAIL="admin@example.com"
MEMORY_THRESHOLD=90 # 内存使用率阈值
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
# 检查内存使用率
check_memory_usage() {
MEM_TOTAL=$(free | grep Mem | awk '{print $2}')
MEM_USED=$(free | grep Mem | awk '{print $3}')
MEM_USAGE=$(echo "scale=2; $MEM_USED * 100 / $MEM_TOTAL" | bc)
echo $MEM_USAGE
}
# 检查是否发生OOM
check_oom_events() {
OOM_COUNT=$(dmesg | grep -c "Killed process")
echo $OOM_COUNT
}
# 获取内存占用TOP进程
get_top_memory_processes() {
ps aux --sort=-%mem | head -6 | tail -5
}
# 主监控逻辑
MEMORY_USAGE=$(check_memory_usage)
OOM_COUNT=$(check_oom_events)
log_message "内存使用率: ${MEMORY_USAGE}%"
# 检查内存使用率
if (( $(echo "$MEMORY_USAGE > $MEMORY_THRESHOLD" | bc -l) )); then
log_message "警告: 内存使用率过高 ${MEMORY_USAGE}%"
# 记录TOP进程
log_message "TOP内存进程:"
get_top_memory_processes >> $LOG_FILE
# 发送告警邮件
cat << EOF | mail -s "内存使用率告警" $EMAIL
内存使用率: ${MEMORY_USAGE}%
时间: $(date)
TOP内存进程:
$(get_top_memory_processes)
建议检查系统内存使用情况
EOF
fi
# 检查OOM事件
LAST_OOM_COUNT_FILE="/tmp/last_oom_count"
if [ -f "$LAST_OOM_COUNT_FILE" ]; then
LAST_OOM_COUNT=$(cat $LAST_OOM_COUNT_FILE)
else
LAST_OOM_COUNT=0
fi
if [ "$OOM_COUNT" -gt "$LAST_OOM_COUNT" ]; then
NEW_OOM_EVENTS=$((OOM_COUNT - LAST_OOM_COUNT))
log_message "检测到 $NEW_OOM_EVENTS 个新的OOM事件"
# 记录最近的OOM事件
dmesg | grep "Killed process" | tail -$NEW_OOM_EVENTS >> $LOG_FILE
# 发送紧急告警
echo "检测到OOM事件,有 $NEW_OOM_EVENTS 个进程被杀死" | mail -s "OOM紧急告警" $EMAIL
fi
echo $OOM_COUNT > $LAST_OOM_COUNT_FILE
内存清理脚本
#!/bin/bash
# memory_cleanup.sh
LOG_FILE="/var/log/memory_cleanup.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
# 清理前记录内存状态
log_message "清理前内存状态:"
free -h >> $LOG_FILE
# 1. 清理系统缓存
log_message "清理系统缓存..."
sync
echo 3 > /proc/sys/vm/drop_caches
# 2. 清理临时文件
log_message "清理临时文件..."
find /tmp -type f -atime +1 -delete 2>/dev/null
find /var/tmp -type f -atime +1 -delete 2>/dev/null
# 3. 清理日志文件
log_message "清理大日志文件..."
find /var/log -name "*.log" -size +100M -exec truncate -s 50M {} \; 2>/dev/null
# 4. 重启占用内存较多的服务
MEMORY_THRESHOLD=1000000 # 1GB in KB
for service in apache2 nginx mysql postgresql; do
PID=$(pgrep $service | head -1)
if [ -n "$PID" ]; then
MEMORY_KB=$(ps -o pid,vsz --no-headers -p $PID | awk '{print $2}')
if [ "$MEMORY_KB" -gt "$MEMORY_THRESHOLD" ]; then
log_message "重启高内存使用服务: $service (内存使用: ${MEMORY_KB}KB)"
systemctl restart $service 2>/dev/null
fi
fi
done
# 清理后记录内存状态
log_message "清理后内存状态:"
free -h >> $LOG_FILE
log_message "内存清理完成"
自动OOM恢复脚本
#!/bin/bash
# oom_recovery.sh
# 检测到OOM后的自动恢复脚本
RECOVERY_LOG="/var/log/oom_recovery.log"
log_recovery() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $RECOVERY_LOG
}
# 检查是否发生了OOM
if dmesg | tail -50 | grep -q "Killed process"; then
log_recovery "检测到OOM事件,开始自动恢复..."
# 1. 清理内存
sync
echo 3 > /proc/sys/vm/drop_caches
log_recovery "已清理系统缓存"
# 2. 检查关键服务状态
CRITICAL_SERVICES=("sshd" "network" "systemd-logind")
for service in "${CRITICAL_SERVICES[@]}"; do
if ! systemctl is-active --quiet $service; then
log_recovery "重启关键服务: $service"
systemctl start $service
fi
done
# 3. 调整swap
if [ $(swapon -s | wc -l) -eq 1 ]; then
log_recovery "系统无swap,尝试创建临时swap"
if [ $(df / | tail -1 | awk '{print $4}') -gt 1048576 ]; then
dd if=/dev/zero of=/tmp/emergency_swap bs=1M count=512 2>/dev/null
chmod 600 /tmp/emergency_swap
mkswap /tmp/emergency_swap >/dev/null 2>&1
swapon /tmp/emergency_swap 2>/dev/null
log_recovery "已创建512MB临时swap"
fi
fi
# 4. 发送通知
echo "系统发生OOM,已执行自动恢复操作" | mail -s "OOM自动恢复通知" admin@example.com
log_recovery "OOM自动恢复完成"
fi
📊 内存管理最佳实践
1. 预防性监控
# 添加定时监控
echo "*/5 * * * * /usr/local/bin/oom_monitor.sh" | crontab -
# 设置内存告警阈值
echo "*/10 * * * * /usr/local/bin/memory_cleanup.sh" | crontab -
2. 应用程序优化
# Java应用内存限制
export JAVA_OPTS="-Xmx2g -Xms1g -XX:+UseG1GC"
# 数据库内存配置
# MySQL配置示例
[mysqld]
innodb_buffer_pool_size = 1G
query_cache_size = 128M
tmp_table_size = 64M
max_heap_table_size = 64M
3. 系统配置优化
# 编辑/etc/security/limits.conf
* soft memlock unlimited
* hard memlock unlimited
* soft as unlimited
* hard as unlimited
# 配置systemd服务内存限制
[Service]
MemoryMax=2G
MemorySwapMax=1G
🚨 应急处理
系统内存严重不足时
- 立即释放内存
```bash
清理所有缓存
echo 3 > /proc/sys/vm/drop_caches
终止非关键进程
pkill -f chrome pkill -f firefox
2. **创建紧急swap**
```bash
dd if=/dev/zero of=/emergency_swap bs=1M count=1024
mkswap /emergency_swap
swapon /emergency_swap
- 重启关键服务
systemctl restart systemd-logind systemctl restart sshd
📚 相关工具
- htop - 交互式进程查看器
- iotop - I/O监控工具
- valgrind - 内存调试工具
- smem - 内存使用报告工具
- pmap - 进程内存映射查看器
- vmstat - 虚拟内存统计
通过合理的内存管理和监控,可以有效预防和处理OOM问题,确保系统稳定运行。
内存管理是Linux系统性能优化的重要方面,理解OOM机制对系统稳定性非常重要。