排查线上OOM:一次被JVM参数坑了的经历

📅 2026/8/26 1:33:34
排查线上OOM:一次被JVM参数坑了的经历
上周四下午快下班的时候收到告警线上一个订单服务的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:MetaspaceSize256m -XX:MaxMetaspaceSize512m问题就出在这儿了。堆设了1.5GMetaspace最大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限制。改法也很简单两步把-Xmx调小给非堆内存留够空间显式限制直接内存大小-XX:MaxDirectMemorySize256m改完之后的参数-Xms1024m -Xmx1024m \ -XX:MetaspaceSize256m -XX:MaxMetaspaceSize384m \ -XX:MaxDirectMemorySize256m \ -XX:UseG1GC \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath/data/logs/heapdump.hprof重新算一下1024 384 200 256 ~100 ≈ 1.96G在2G限制内留了一点余量。上线之后观察了两天没再出现OOMKilled的情况。踩坑总结容器环境下-Xmx绝对不能设到接近容器内存限制的值建议-Xmx不超过容器limits.memory的60%~70%剩下的留给非堆和系统开销如果用了NIO或者Netty一定要显式设置-XX:MaxDirectMemorySize排查容器OOM的时候不要只看JVM的GC日志还要看/sys/fs/cgroup/memory/下面的统计信息其实这个问题不算新但每次换团队、换项目的时候总能看到有人踩这个坑。写下来记录一下也希望能帮到遇到同样问题的人。