← 返回首页

一台 2G 小机器,两次"假死",我终于查清了元凶

服务器运维 亲身经历 公开
内容简要
一台轻量应用服务器两次出现'能 ping 通、SSH 连不上、像死机一样',最终发现是两个未设内存上限的 Spring Boot 服务吃掉了 Swap,导致系统变慢。
坑点
未给 Spring Boot 服务设置 JVM 内存上限,导致 Swap 被吃满,系统变慢。
注意事项
  • 为 Spring Boot 服务设置 JVM 内存上限 (-Xmx)。
  • 确保所有服务都通过 systemd 管理,并验证 Main PID。
  • 使用 free -h 命令查看 Swap 使用情况,而非 systemctl status 中的 swap peak。
  • 多服务同机时,注意内存'合计算账',避免超卖。
  • 使用 daemon-reload 全量重读配置,并使用 restart 使配置生效。
  • 在内存紧张时谨慎关闭 Swap,避免 OOM。

一台 2G 小机器,两次"假死",我终于查清了元凶

摘要:一台轻量应用服务器,两次出现"能 ping 通、SSH 连不上、像死机一样",重启后恢复正常。最后发现,凶手不是 OOM,不是 sshd,而是我自己跑的两个 Spring Boot 服务——没给 JVM 设内存上限,硬生生把 1GB 的 Swap 吃到 900MB,整机被拖慢到连不进去。

一、开局:服务器"失联"了

那天下午,我像往常一样连服务器,突然发现:

  • ping 通 ✅
  • SSH 连不上 ❌
  • 控制台 VNC 也卡得动不了 ❌

第一反应是:坏了,被攻击了?被挖矿了?还是内核 panic 了?

赶紧重启,机器起来,一切正常。我心想,大概是偶然吧。

结果过了一阵,又来一次,表现一模一样。

这下不能当偶然了。


二、第一直觉:是不是被 OOM 杀进程了?

搞 Linux 的都知道一句话:"连不上,先查 OOM。"

于是我连敲三条命令:

dmesg -T | grep -i "oom|killed process"
journalctl -k | grep -i "oom|killed process"
journalctl -b -1 | grep -i "oom|killed"

全!都!没!有!输出!

我愣住了。没有 OOM,没有 killed,连一条内存紧张的痕迹都没有。

踩坑 1:别凭经验下结论。dmesg 缓冲区会滚动覆盖,-b -1 只能看上一次启动。你没查到,不代表没发生过。

三、第二个线索:有个服务被 "KILL" 了

journalctl -b -1 里倒是有一条奇怪的记录:

ydt.service: Main process exited, code=killed, status=9/KILL

status=9/KILL,就是被 SIGKILL 强杀了。

我第一反应是:这是内核 OOM Killer 干的吧?​ 于是我继续往"内存"这条路上死磕。

后来又看到 systemctl status 里一行:

Memory: 38.0M (peak: 308.3M swap: 204.9M swap peak: 211.9M)

我大喊一声:"找到了!Swap 用满了!"

然后我犯了一个低级错误:我根据这条 swap peak: 211.9M,断定这台机器 Swap 只有 211MB,还被吃满了。


四、真相:Swap 根本不是 211MB

后来我跑了这条命令:

free -h

输出让我脸一红:

Mem:     1.9Gi   used 1.4Gi   free 79Mi   available 544Mi
Swap:    1.0Gi   used 900Mi   free 124Mi

Swap 有 1GB,而且已经用了 900MB,只剩 124MB!

踩坑 2:systemctl status 里的 swap peak 是"这个服务启动以来"的峰值,不是系统的 Swap 总量。我拿它当总量,直接把诊断带偏了。要看 Swap,老老实实 free -h。

那个 211.9M,只是那次 systemctl 采样时服务用过的 Swap 峰值。真正的 Swap 窟窿,远比我想象得大。


五、最迷惑的一幕:到底谁启动的 Java?

我想给 JVM 加内存限制,于是去看进程:

ps -ef | grep java

输出两个:

oadba  1057  ...  java -jar ydt-server.jar
oadba  4128  ...  java -jar pitlog.jar

我一眼看到 ppid 都是 1,心想:

"父进程是 1 号,那就是 systemd 管的,两个都是!"

我又错了。

PPID=1 只能说明"它爹死了,被 init 收养了",不能证明是 systemd 亲手拉的。你用 nohup ./xxx.sh & 起的进程,退出终端后一样会变成 PPID=1。

正确的验证方式只有一个:

systemctl status ydt.service    | grep "Main PID"
systemctl status pitlog.service | grep "Main PID"

结果啪啪打脸:

服务systemd 认不认
ydt-server PID 1057✅ Main PID: 1057,systemd 亲生的
pitlog PID 4128❌ 服务根本不存在,脚本裸奔
踩坑 3:PPID=1 ≠ systemd 管理。要验证,就看 systemctl status 显示的 Main PID 和 ps 的 PID 对不对得上。

六、元凶现身:吃内存的不是我想的那个

再看一眼那两个 Java:

pitlog    4128  oadba  27.0%  544MB   java -jar pitlog.jar
ydt-server 1057  oadba  10.7%  215MB   java -jar ydt-server.jar

pitlog 占了 544MB,是 ydt 的 2.5 倍!

而且它:

  • 没 systemd 服务 ❌
  • 没 -Xmx ❌
  • 崩了没人拉 ❌
  • 退出终端可能就没了 ❌

我之前把火力全集中在 ydt 上,方向偏了一半。真正的"内存大户"是那个我以为"只是个小日志程序"的 pitlog。


七、完整因果链,终于串起来了

两个 Java 都没设 -Xmx
        ↓
内存慢慢涨,堆外 + 堆一起往上堆
        ↓
触发 Swap,1GB 被吃到 900MB
        ↓
频繁换页,磁盘 I/O 被打满
        ↓
所有进程(包括 sshd)都卡在 I/O 上
        ↓
你这边:ping 通,但 SSH 握手完不成 → "连不上"
        ↓
重启 → Swap 清空 → 一切恢复 → 过几天又来

这解释了所有症状:

  • ✅ 为什么 ping 通但连不上(sshd 没死,只是慢)
  • ✅ 为什么重启就好(Swap 清空了)
  • ✅ 为什么是"间歇性复发"(内存是慢慢涨上来的)
  • ✅ 为什么没 OOM 记录(不是被杀,是慢死)
踩坑 4:Swap 是"慢性毒药"。它不让程序干脆崩掉,而是把整机拖慢到"连运维都登不进去",比直接 OOM 更难排查。

八、解法:四步止血

1️⃣ 两个 Java 都交给 systemd,并设死内存

关键是算账。我这 1.9GB 内存,扣掉系统预留和堆外内存,两个 Java 的 -Xmx 合计不能超过约 512MB:

# ydt.service / pitlog.service 的 drop-in
[Service]
Environment="JAVA_OPTS=-Xms128m -Xmx256m -XX:+UseG1GC -XX:MaxMetaspaceSize=96m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/springboot/heapdump"

两个都是 -Xmx256m,加起来正好 512m。

踩坑 5:别给 -Xmx512m 就完事。这台机器两个 Java,你各给 512m,堆合计 1GB,再加堆外内存直接超卖,Swap 照样打满。多服务同机,一定要"合计"着算。

2️⃣ 新建服务后,记住这条铁律

systemctl daemon-reload    # 让 systemd 重读配置(全量,不会重启任何服务)
systemctl restart xxx      # 让新配置对运行中的服务生效
踩坑 6:daemon-reload 是"全量"的,不能指定单个 service,但它不会重启任何服务,大胆用。restart 才是"只针对那一个服务生效"的动作。很多人改完只 daemon-reload,然后纳闷参数为啥没变。

3️⃣ 关掉 Swap

swapoff -a && sed -i '/swap/s/^/#/' /etc/fstab

注意:别在内存紧张时直接关。​ 先把 Java 压到 256m、腾出余量,再 swapoff -a,否则它会把 900MB 数据往内存倒,可能当场 OOM。

4️⃣ 顺手挡 SSH 爆破

journalctl -u sshd --since "7 days ago" | grep -c "Failed password"

一跑,几千条。难怪连接槽老满。装个 fail2ban,5 分钟搞定。


九、这次排查,我学到的 6 条教训

#教训
1dmesg 无输出 ≠ 没发生过 OOM,缓冲区早被冲掉了
2看 Swap 用 free -h,别信 systemctl status 里的 swap peak
3PPID=1 ≠ systemd 管理,看 Main PID 对不对得上
4Swap 让机器"慢慢死",比 OOM 更难查,小内存机器建议关
5多 Java 同机,内存要"合计算账",别各写各的
6daemon-reload 是全量重读;要生效还得 restart

十、一句话总结

这次"连不上"的元凶,不是黑客,不是 OOM,不是 sshd,是我自己那两个没戴"内存紧箍咒"的 Spring Boot。给 JVM 设个 -Xmx,把服务交给 systemd,关掉 Swap——三次复发,一刀切断。
本文内容仅代表作者个人观点、经历或转述,不代表任何官方立场,仅供参考,不构成任何专业建议。据此操作产生的风险由读者自行承担。
发布于 2026-11-06 16:29:34 ID: 46