排查线上OOM:一次被JVM参数坑了的经历
2026/8/26 1:33:24 网站建设 项目流程

上周四下午快下班的时候,收到告警,线上一个订单服务的Pod频繁重启。

一开始以为是GC的问题,因为之前也遇到过类似的情况——堆内存不够,Full GC频繁,最后OOM。所以第一反应是去看Grafana上的JVM监控。

结果一看,堆内存使用率才60%左右,GC频率也正常。这就奇怪了。

然后去看Pod的events,发现重启原因写的是OOMKilled

这就很迷惑了——JVM自己没报OOM,但是容器被kill了。

后来才反应过来,这是容器层面的OOM,不是JVM层面的。也就是说,整个Pod的内存使用超过了K8s配置的resources.limits.memory,被cgroup直接干掉了。

去查了一下这个服务的部署配置:

resources: limits: memory: 2Gi cpu: 1000m requests: memory: 1Gi cpu: 500m

容器限制是2G。再看JVM启动参数:

-Xms1536m -Xmx1536m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m

问题就出在这儿了。

堆设了1.5G,Metaspace最大512M,再加上线程栈、直接内存、JVM自身开销这些,实际进程占用的内存早就超过2G了。

之前一直觉得只要-Xmx不超过容器限制就没事,但实际上JVM进程的内存远不止堆这一块。

几个容易忽略的内存占用:

  • 线程栈:默认每个线程1M(-Xss),如果线程数多,这块占用不小。我们那个服务线程池配了200个核心线程,光线程栈就200M了
  • 直接内存(Direct Memory):用了Netty做网络通信,直接内存默认最大等于-Xmx,又是一大块
  • Metaspace:加载的类多的话,这块也不小
  • JVM自身:Code Cache、GC数据结构等,大概几十到上百M

所以一个粗略的估算公式:

JVM进程总内存 ≈ 堆内存 + Metaspace + 线程栈(线程数 × Xss) + 直接内存 + JVM自身开销

按我们那个配置算一下:1536 + 512 + 200 + 1536(直接内存)+ ~100 ≈ 3.8G,远超容器的2G限制。

改法也很简单,两步:

  1. -Xmx调小,给非堆内存留够空间
  2. 显式限制直接内存大小:-XX:MaxDirectMemorySize=256m

改完之后的参数:

-Xms1024m -Xmx1024m \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=384m \ -XX:MaxDirectMemorySize=256m \ -XX:+UseG1GC \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/data/logs/heapdump.hprof

重新算一下:1024 + 384 + 200 + 256 + ~100 ≈ 1.96G,在2G限制内,留了一点余量。

上线之后观察了两天,没再出现OOMKilled的情况。

踩坑总结:

  • 容器环境下,-Xmx绝对不能设到接近容器内存限制的值
  • 建议-Xmx不超过容器limits.memory60%~70%,剩下的留给非堆和系统开销
  • 如果用了NIO或者Netty,一定要显式设置-XX:MaxDirectMemorySize
  • 排查容器OOM的时候,不要只看JVM的GC日志,还要看/sys/fs/cgroup/memory/下面的统计信息

其实这个问题不算新,但每次换团队、换项目的时候,总能看到有人踩这个坑。写下来记录一下,也希望能帮到遇到同样问题的人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询