部落冲突皇室战争
1.09GB · 2025-09-23
很多人以为“调优 = 调 -Xmx”,这是最表层的理解。
真正完整的调优包含四层:
层级 | 调整内容 | 目标 | 工具/参数 |
---|---|---|---|
第一层:空间结构 | 新生代/老年代大小、Survivor 比例 | 控制对象流动节奏,避免过早晋升或空间浪费 | -Xmn , -XX:NewRatio , -XX:SurvivorRatio |
第二层:晋升策略 | 对象在新生代“熬”几次 GC 才进老年代 | 过滤短命对象,减轻老年代压力 | -XX:MaxTenuringThreshold , -XX:+UseAdaptiveSizePolicy |
第三层:GC算法 | 选择 Serial / Parallel / G1 / ZGC | 匹配业务对“延迟”或“吞吐”的需求 | -XX:+UseG1GC , -XX:+UseZGC |
第四层:行为目标 | 设定最大停顿时间、Region大小、回收粒度 | 精细化控制 GC 行为,逼近业务 SLA | -XX:MaxGCPauseMillis , -XX:G1HeapRegionSize |
-Xms4g -Xmx4g -Xmn512m # 新生代仅 512M
→ Eden 秒满,每秒多次 Minor GC;
→ 虽单次快,但累积 STW 时间长,CPU 被 GC 线程吃满;
→ Survivor 放不下存活对象,大量短命对象提前晋升老年代。
-Xmn3g -Xmx4g # 老年代只剩 1G
→ 缓存、连接池、大对象稍多就撑满;
→ Full GC 触发,STW 1~5 秒,用户请求超时;
→ 如果用 CMS,还会触发 Concurrent Mode Failure,更卡。
-XX:MaxTenuringThreshold=1 # 活一次就进老年代
→ 本该在新生代死的对象,全跑老年代去了;
→ Full GC 时要扫描/移动这些“垃圾”,效率极低;
→ Full GC 时间变长,频率变高。
算法思想相同,但“执行引擎”不同 —— 这才是核心差异。
场景 | 错误选择 | 后果 | 正确选择 |
---|---|---|---|
高并发 Web 服务 | 用 Serial GC | 单线程 STW,每次 GC 全服务卡住 | G1 / ZGC(并发、低停顿) |
大数据批处理 | 用 CMS | 碎片多 + Full GC 频繁,总耗时更长 | Parallel GC(吞吐优先) |
大堆低延迟 | 用 Parallel GC | STW 太长,无法满足 SLA | ZGC / Shenandoah(亚毫秒停顿) |
不调优 = 让 JVM 用“猜的策略”硬扛你的业务 → 轻则性能差,重则 OOM 崩溃。
必须监控的核心指标:
指标 | 工具 | 说明 |
---|---|---|
Minor GC 频率 & 耗时 | jstat -gc <pid> | 频繁 or 耗时长 → 调新生代大小 or GC 算法 |
Full GC 频率 & 耗时 | jstat -gc <pid> | 频繁 → 老年代太小 or 晋升太快 or GC 算法错 |
老年代使用率曲线 | Grafana / VisualVM | 是否平稳?还是阶梯式暴涨? |
每次 GC 后老年代回收率 | GC 日志 | 回收率低 → 老年代全是“真长期对象” or 被短命对象污染 |
示例 jstat:
S0C S1C S0U S1U EC EU OC OU YGC YGCT FGC FGCT
10752.0 10752.0 0.0 8960.0 65536.0 65536.0 175104.0 174890.1 123 2.452 5 8.732
→ EU 满 → 触发 YGC;OU 接近 OC → 危险,快 Full GC。
-Xmn2g # 总堆 4G 时,新生代占一半
-XX:NewRatio=3 # 老年代 : 新生代 = 3:1
-XX:SurvivorRatio=6 # Eden:S0:S1 = 6:1:1 (默认 8:1:1)
-XX:MaxTenuringThreshold=20 # 默认 15,可适当调高
业务类型 | 推荐 GC | 关键参数 | 理由 |
---|---|---|---|
高并发 Web / API | G1GC | -XX:MaxGCPauseMillis=100 | 可控停顿,Region 回收,适合大堆低延迟 |
大数据批处理 | Parallel GC | 无特殊参数 | 最大吞吐量,STW 长但总时间短 |
超大堆 + 极致低延迟 | ZGC | -XX:+UseZGC -Xmx32g | 亚毫秒停顿,适合 >16G 堆 |
客户端 / 小应用 | Serial GC | 无 | 单线程,开销小,适合 <4G 堆 |
调完必须验证:
工具:
jstat -gcutil <pid> 1000
:实时看 GC 利用率;jmap -histo:live <pid>
:看存活对象类型分布;开启 GC 日志(必备):
-Xloggc:/logs/gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution # 看对象年龄分布,关键!
错误做法 | 后果 | 正确做法 |
---|---|---|
只调 -Xmx,不调 -Xmn | 新生代比例失调,GC 更频繁 | 同步调整新生代大小,保持合理比例 |
SurvivorRatio 设太小(如 2) | Survivor 太小,对象秒晋升 | 保持默认 8 或微调到 6~7 |
MaxTenuringThreshold 设 0 或 1 | 对象“秒进”老年代,污染严重 | 至少设 5 以上,观察年龄分布后再调 |
业务敏感还用 Parallel GC | STW 太长,请求超时 | 换 G1 或 ZGC,设 MaxGCPauseMillis |
不看 GC 日志,盲目调参 | 调了也不知道有没有用 | 必须开启 GC 日志,用工具对比验证 |