加载中
🤖
AI审核中

一次OOM排查实录

  Java   12分钟   109浏览   0评论
AI
AI智能摘要
正在分析文章内容

一、现象:项目跑着跑着,日志突然断了

昨晚线上告警,博客系统毫无征兆地停止了服务。打开日志文件,最后一条日志写了一半就戛然而止,没有任何 Tomcat stoppedShutting down 的关闭记录

2026-04-21 10:45:01.223 DEBUG ... io.lettuce.core.RedisChannelHandler      : dispatching command AsyncCommand [type=GET,

日志在这里被硬生生截断。这种"猝死"特征,基本可以排除应用内部的优雅停机,而是进程被外部强制终止。

二、排查:锁定 Linux OOM Killer

既然是强制终止,首先怀疑 Linux OOM Killer(Out of Memory Killer)。当系统内存耗尽时,内核会挑选评分最高的进程直接 kill -9,这个过程不会触发 JVM 的 ShutdownHook,因此应用日志里看不到任何关闭痕迹。

登录服务器,查看内核日志:

journalctl -k | grep -i 'oom'

输出触目惊心——这台服务器简直就是 OOM Killer 的"屠宰场":

Apr 19 08:05:25 ... Out of memory: Kill process 14244 (mysqld) score 256
Apr 20 17:44:29 ... Out of memory: Kill process 4745 (mysqld) score 244
Apr 21 08:24:34 ... Out of memory: Kill process 12280 (mysqld) score 235
Apr 21 21:48:35 ... Out of memory: Kill process 16066 (mysqld) score 267
Apr 22 16:58:25 ... Out of memory: Kill process 19621 (mysqld) score 242
...
Apr 23 01:30:17 ... Out of memory: Kill process 16631 (java) score 239
Apr 23 01:30:17 ... Killed process 16631 (java), UID 0, total-vm:3072252kB, anon-rss:442820kB

破案了:PID 16631 正是我的博客进程。 一个月内,OOM Killer 在这里触发了 20 多次,MySQL 被杀了无数次,这次终于轮到了 Java。

三、根因:2G 内存的"螺蛳壳"里塞了太多东西

这台服务器配置是 2核2G,却跑了:

  • 3 个 Spring Boot 项目(博客 + 扑克记账 + AI 聊天系统)
  • MySQL 8.0
  • 阿里云监控(AliYunDun、argusagent)

1. JVM 内存未限制

三个 Spring Boot 项目启动时都没有 -Xmx 参数,JVM 默认会申请接近物理内存上限的堆空间,导致虚拟内存膨胀到 3GB 级别,成为 OOM Killer 的高分目标。

2. MySQL 8.0 是内存大户

MySQL 8.0 比 5.7 更吃内存,默认开启的 Performance Schema alone 就能吃掉 100~400MB,加上默认 table_open_cache = 4000max_connections = 151,在小内存机器上就是一颗定时炸弹。

3. 没有 Swap

free -h 显示 Swap: 0B。这意味着内存一旦耗尽,内核没有任何缓冲余地,只能直接杀人。

四、极限优化:把 2G 内存榨出花来

1. 限制三个 JVM 的内存

原则:-Xms-Xmx 设成一样,避免运行时动态扩缩容导致内存抖动。

# 博客项目(主站,访问量最大,给 512M)
nohup java -Xms512m -Xmx512m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m \
  -jar /myworkspace/blog.jar --server.port=8081 \
  > /myworkspace/logs/app-$(date +%Y-%m-%d).log 2>&1 &

# 项目二(256M)
nohup java -Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m \
  -jar /myworkspace/puke/card-accounting-1.4.1.jar --server.port=8085 \
  > /myworkspace/logs/card-accounting-$(date +%Y-%m-%d).log 2>&1 &

# 项目三(256M)
nohup java -Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m \
  -jar /myworkspace/chat/ai-chat-system-1.0.0.jar --server.port=8083 \
  > /myworkspace/logs/ai-chat-system-$(date +%Y-%m-%d).log 2>&1 &

2. MySQL 8.0 内存瘦身

编辑 /etc/my.cnf,在 [mysqld] 段添加:

[mysqld]
# 核心:InnoDB 缓冲池,这是内存占用的大头
innodb_buffer_pool_size = 128M

# 连接数限制,太多连接会耗尽内存
max_connections = 30

# MySQL 8.0 默认开启,非常吃内存,小机器务必关闭
performance_schema = OFF

# 8.0 默认 4000,2G 内存直接爆炸
table_open_cache = 200
table_definition_cache = 200

# 连接级缓冲(每个连接都分配,不要设大)
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 1M
join_buffer_size = 1M

# 临时表内存限制
tmp_table_size = 16M
max_heap_table_size = 16M

innodb_log_buffer_size = 8M
thread_cache_size = 8

注意:MySQL 8.0 已移除查询缓存,不要写 query_cache_* 相关参数,会启动报错。

验证语法后重启:

sudo mysqld --validate-config
sudo systemctl restart mysqld

3. 添加 Swap 兜底

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

五、验证:内存终于健康了

优化后执行 free -h

              total        used        free      shared  buff/cache   available
Mem:           1.7G        1.2G        201M        464K        324M        383M
Swap:          2.0G          0B        2.0G
  • available: 383MB —— 健康,超过 300MB 安全线
  • Swap: 2.0G —— 已启用,尚未使用(理想状态)
  • 三个 Java 进程 + MySQL 稳定运行,不再被 OOM Killer 骚扰

六、踩坑总结

坑点 教训
JVM 不限制内存 小内存服务器必须加 -Xms-Xmx,否则虚拟内存膨胀到 3GB,OOM Killer 第一个找它
MySQL 8.0 默认配置 performance_schema = ONtable_open_cache = 4000 在小机器上是灾难,必须手动限制
不配置 Swap 2G 内存跑 3 个 Java + MySQL,Swap 是最后的保命符
日志不落地 生产环境必须用 nohup ... > log.file 2>&1 & 重定向,否则排查问题时无迹可寻
只看应用日志 应用日志被截断时,一定要去查系统日志 journalctl -k/var/log/messages

七、后续监控命令(收藏备用)

# 看整体内存
free -h

# 看各进程实际物理内存占用
ps aux --sort=-%mem | head -10

# 看 Swap 使用情况(如果 used 超过 500MB,说明物理内存不够了)
free -h | grep Swap

# 看系统是否有 OOM 记录
sudo dmesg | grep -i 'oom'

八、写在最后

2G 内存跑 3 个 Spring Boot + MySQL 8.0,本质上是在"螺蛳壳里做道场"。这次优化虽然把内存压到了极限,但只是权宜之计。

如果业务继续增长,最终的解法只有两个:

  1. 把 MySQL 迁到云数据库 RDS,释放本地 400MB+ 内存;
  2. 把 ECS 升到 2核4G,一劳永逸。

希望这篇踩坑记录能帮到同样在小内存服务器上挣扎的朋友。如果你也遇到了"项目跑着跑着就停了"的诡异现象,不妨先查查 OOM Killer 的案底。

如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
  0 条评论
AI助手
召田最帅boy的小助手
🤖
我是召田最帅boy的小助手
我已经阅读了这篇文章,可以帮您:
理解文章内容 · 解答细节问题 · 分析核心观点