一、现象:项目跑着跑着,日志突然断了
昨晚线上告警,博客系统毫无征兆地停止了服务。打开日志文件,最后一条日志写了一半就戛然而止,没有任何 Tomcat stopped 或 Shutting 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,因此应用日志里看不到任何关闭痕迹。
登录服务器,查看内核日志:
bash
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 = 4000、max_connections = 151,在小内存机器上就是一颗定时炸弹。
3. 没有 Swap
free -h 显示 Swap: 0B。这意味着内存一旦耗尽,内核没有任何缓冲余地,只能直接杀人。
四、极限优化:把 2G 内存榨出花来
1. 限制三个 JVM 的内存
原则:-Xms 和 -Xmx 设成一样,避免运行时动态扩缩容导致内存抖动。
bash
# 博客项目(主站,访问量最大,给 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] 段添加:
ini
[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_* 相关参数,会启动报错。
验证语法后重启:
bash
sudo mysqld --validate-config
sudo systemctl restart mysqld
3. 添加 Swap 兜底
bash
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 = ON、table_open_cache = 4000 在小机器上是灾难,必须手动限制 |
| 不配置 Swap | 2G 内存跑 3 个 Java + MySQL,Swap 是最后的保命符 |
| 日志不落地 | 生产环境必须用 nohup ... > log.file 2>&1 & 重定向,否则排查问题时无迹可寻 |
| 只看应用日志 | 应用日志被截断时,一定要去查系统日志 journalctl -k 和 /var/log/messages |
七、后续监控命令(收藏备用)
bash
# 看整体内存
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,本质上是在"螺蛳壳里做道场"。这次优化虽然把内存压到了极限,但只是权宜之计。
如果业务继续增长,最终的解法只有两个:
- 把 MySQL 迁到云数据库 RDS,释放本地 400MB+ 内存;
- 把 ECS 升到 2核4G,一劳永逸。
希望这篇踩坑记录能帮到同样在小内存服务器上挣扎的朋友。如果你也遇到了"项目跑着跑着就停了"的诡异现象,不妨先查查 OOM Killer 的案底。