-Xmx应根据可用内存和环境限制合理设置:物理机≤70%的可用内存可用内存~80%的容器小于memory limit预留10%~20%缓冲;-Xms和-Xmx必须相等,以避免性能抖动;还需要预留元空间、直接内存等非堆积费用,并通过RSS和GC日志验证实际效果。
看看物理内存和系统费用-Xmx
不要一上来就设置-Xmx16g,首先要看机器还剩多少真正可用的内存。使用free -m看available列,不是total;在容器里要多加小心——Kubernetes 的memory limit是硬上限,JVM 超了会被 OOMKilled,而-Xmx必须严格小于(建议留住) 10%~20% 缓冲)。例如容器 limit 是 16Gi,-Xmx最多设到12g~14g。
- 物理机:推荐
-Xmx≤ 可用内存的 70%~80%,给 OS、页面缓存,当地过程留出足够的空间 - 容器环境:必要
-Xmx < memory limit,否则 GC 还没来得及跑,cgroup 已经杀进程 - 混合场景(如和 Redis、Nginx 同机):按实际压测后 RSS 峰值反推,不要相信“理论价值”
-Xms和-Xmx一定要相等
不等于主动引入性能抖动:堆从-Xms当扩容开始时,会触发额外的 Full GC 或 STW(尤其 ParallelGC),且 JVM 需反复向 OS 申请内存页面,延迟无法控制。G1 虽然稍微好一点,但还是不推荐动态伸缩。
- 所有的生产服务都设置相同的值,例如:
-Xms8g -Xmx8g - 小内存服务(≤2g)可以放宽,但也要避免
-Xms512m -Xmx2g这种跨度过大的组合 - Spring Boot 注:若使用
spring-boot-starter-actuator+management.endpoint.heapdump.show-info=true,堆越大,heap dump 文件生成越慢,可能会阻塞线程
不要忽视元空间和直接内存的“隐形费用”
-Xmx只管堆,但 JVM 还要吃元空间(-XX:MaxMetaspaceSize)、线程栈(-Xss)、直接内存(-XX:MaxDirectMemorySize)、JIT 编译代码缓存等。这些加起来可能比堆多,特别是在大量反射、动态代理、Netty 场景下。
- 典型搭配:
-Xmx8g -XX:MaxMetaspaceSize=512m -XX:MaxDirectMemorySize=2g - 未设
-XX:MaxMetaspaceSize?当类加载器泄漏时,元空间无限增长,最终触发java.lang.OutOfMemoryError: Metaspace - Netty 常用坑的应用:
-Xmx4g却没调-XX:MaxDirectMemorySize,结果堆不满,直接内存先爆,报告OutOfMemoryError: Direct buffer memory
验证是否正确:盯着看 GC 日志和 RSS 实际值
写参数不等于生效,更不用说合理了。真正要看的是 JVM 进程的 RSS(Resident Set Size)是否稳定、Full GC 是否频繁,老年占用率是否长期, >70%。
- 启动参数:
-Xlog:gc*:file=gc.log:time,tags,level(JDK 11+),不要用旧的-XX:+PrintGCDetails - 在操作过程中检查真实内存:
ps -o pid,rss,comm -p <pid>,对比rss/1024(MB)和-Xmx差值太大,说明非堆内存吃多了。 - 老年代持续 >85%?说明
-Xmx其实还不够,或者有内存泄漏,光调大是没有用的
跳过的最常见步骤是未确认容器 cgroup memory.max 根据经验设定实际限制值-Xmx。一上线就被 kill,连 GC 打出日志已经太晚了。