核心是让对象在年轻代“自然死亡”并防止老年代被误灌,需固定堆大小、合理分配年轻代、调大Survivor区、设元空间硬上限、监控GC日志及线程栈内存。

核心是让对象在年轻代“自然死亡”,同时堵住老年代被误灌的漏洞——堆和元空间配得再大,若结构失衡或类加载失控,照样触发 Full GC。
堆内存必须固定且比例合理
初始堆(-Xms)和最大堆(-Xmx)务必设为相同值,比如 -Xms6g -Xmx6g。动态扩容会引发额外 GC 开销,尤其在高并发流量突增时,容易触发老年代压力连锁反应。
- 总堆建议占物理内存的 50%~70%,单实例不超过 16GB;16GB 服务器上独占应用可设为 8GB,留出空间给元空间、线程栈和 OS 缓存
- 年轻代不能靠经验拍脑袋:Web 类服务对象生命周期短,推荐年轻代占堆 1/3~1/2(如 6GB 堆配 -Xmn2g 或 -XX:NewRatio=2)
- 避免 Survivor 区成为瓶颈——默认 -XX:SurvivorRatio=8(Eden:S0:S1 = 8:1:1)常导致 Survivor 太小,建议调至 4 或 3,等效扩大存活区容量,减少因撑满导致的早熟晋升
元空间必须设硬上限并匹配类加载特征
Spring Boot、动态代理、热部署多的应用,元空间极易无节制增长,一旦触达阈值就会强制 Full GC。这不是堆不够,而是元空间“越界”了。
- 显式设置 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m,避免启动期反复调整和溢出
- 若应用含大量 Starter、自定义注解或运行时生成类(如 MyBatis MapperProxy),可放宽到 768m,但不建议超过 1GB
- 注意:元空间使用的是本地内存(Native Memory),和堆互不抢占,但共享进程虚拟地址空间——容器中内存限制严格时,超限直接 OOMKill
配合 GC 日志验证“不误灌”效果
参数调完不算完,得看老年代是否真的安静下来。重点盯两个指标:
立即学习“Java免费学习笔记(深入)”;
- 每次 Minor GC 后晋升到老年代的字节数(jstat 输出中的 G1U 或 OU 变化量),稳定低于 500KB 是健康信号
- 老年代使用率(OU)长期维持在 30%~60%,缓慢上升而非阶梯式跳变——若每小时涨 10%,说明仍有隐性长生命周期对象或泄漏
- 开启日志:-Xloggc:/data/jvm/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps,重点关注 “Full GC (Metadata GC Threshold)” 这类元空间触发的 Full GC 记录
别漏掉线程栈与整体内存协同
高并发下线程数飙升,-Xss 默认 1MB 容易吃掉数百 MB 内存。若单个请求调用链不深,可安全降至 -Xss256k,释放空间给堆和元空间。
- 估算公式:总内存 ≈ 堆 + 元空间上限 + (线程数 × -Xss)
- Tomcat 默认 200 线程 × 1MB = 200MB;改用 256k 后仅占 50MB,省下的 150MB 足够缓冲一次突发流量
- 容器环境务必预留 20%~30% 内存给 OS 和其他进程,避免因内存争抢导致 JVM 被 OOMKilled


















