JVM 调优常用参数有哪些?
简化版
调优参数分三类:堆大小(-Xms/-Xmx)、分代与元空间(-Xmn、-XX:MetaspaceSize)、收集器与 GC 目标(-XX:+UseG1GC、-XX:MaxGCPauseMillis),再加上 GC 日志和自动 dump 参数用于排查。但真正的调优心法是:先有目标和监控数据,再动参数,不要凭感觉调。
详细版
① 堆内存
-Xms/-Xmx:初始堆 / 最大堆,生产环境建议设成相同值,避免运行期堆动态伸缩带来的抖动和额外 GC。
② 分代与元空间
-Xmn:年轻代大小(或-XX:NewRatio设老年代/年轻代比例);-XX:SurvivorRatio:Eden 与单个 Survivor 的比例;-XX:MetaspaceSize/-XX:MaxMetaspaceSize:元空间初始/上限,防止类过多撑爆(元空间默认用本地内存,不设上限可能吃满物理内存)。
③ 收集器与 GC 行为
-XX:+UseG1GC:启用 G1(JDK 9+ HotSpot server 配置默认);-XX:MaxGCPauseMillis:给 G1 设目标最大停顿;-XX:MaxTenuringThreshold:晋升年龄阈值。
④ 排查诊断
-Xlog:gc*(JDK 9+,旧版-XX:+PrintGCDetails):打印 GC 日志;-XX:+HeapDumpOnOutOfMemoryError+-XX:HeapDumpPath:OOM 时自动 dump 堆,事后用 MAT 分析。
完整版教学
一、调优的正确姿势:先定目标,再看数据,最后调参
这道题面试官真正想听的不是背参数,而是你有没有调优的方法论。参数谁都能查,思路才是水平:
- 明确目标:你要的是高吞吐(批处理、离线计算,追求单位时间处理量)还是低延迟(在线服务,追求短 STW)?两者常常互斥,目标不同调法完全不同。
- 建立监控:先有 GC 日志、堆使用曲线、STW 时长、各代占用等数据,看清楚瓶颈在哪。
- 针对性调整:根据数据动一两个参数,然后再压测对比,验证是否真的变好。
- 迭代:一次只改一个变量,否则无法判断是哪个改动起了作用。
记忆点:没有监控数据的调优就是玄学。「狂调参数碰运气」是最典型的错误。
二、吞吐 vs 低延迟:两条路线
- 追吞吐:堆可以大一点、年轻代大一点(减少 GC 频率),可选 Parallel GC,容忍偶尔较长 STW 换取总吞吐。
- 追低延迟:用 G1 / ZGC / Shenandoah 这类并发收集器,设
MaxGCPauseMillis控制单次停顿,目标是在较大堆下维持低停顿,但实际结果仍受 JDK、硬件、负载和内存余量影响。
选收集器比调一堆细参数更关键——选对收集器,很多细节参数根本不用碰。
三、几个高频「一动就见效」的点
-Xms=-Xmx:最常见的稳态配置。堆固定,省去反复扩缩容。-Xmx别贴着物理内存设:要给元空间、线程栈、直接内存、堆外缓存留余量,否则容器里会被 OOMKilled。- 元空间设上限:
-XX:MaxMetaspaceSize,防止类加载器泄漏无限吃本地内存。 - 务必开 GC 日志和 OOM 自动 dump:这俩是出事后唯一的现场证据,上线前就该配好。
四、出了问题,先看日志和 dump,别急着调参
真实排查流程通常是:
- 频繁 Full GC / 内存涨 → 抓 heap dump,用 MAT 找「最大保留集」和 GC Root 引用链,定位是谁一直握着对象不放(泄漏根因)。
- STW 长 → 看 GC 日志里是哪类 GC、停顿多久、各代回收前后大小,判断是年轻代太小频繁晋升,还是老年代满了。
- 元空间 OOM → 查是不是动态代理/字节码生成/类加载器没释放。
修复往往是改代码(消除泄漏、限制缓存大小、分页处理),而不是把 -Xmx 一味调大——调大只是把 OOM 推迟,泄漏还在。
一次有效对比必须固定业务窗口。例如同为每秒 2,000 次请求,调整前 GC 暂停 P99 为 180 ms、吞吐 96%,调整后 P99 降到 90 ms 但吞吐跌到 88%,这不是简单的“优化成功”,而是用 8 个百分点吞吐换尾延迟。是否接受取决于接口 SLA、CPU 余量与成本预算。若两次测试的请求量或数据集不同,参数归因就没有意义。
暂停 P99 改善 = (180 - 90) / 180 = 50%
吞吐损失 = 96% - 88% = 8 个百分点
五、先算容量模型,再讨论参数
假设服务稳定流量下每秒分配 600 MB,Young GC 后存活率 5%,则每秒约有 30 MB 对象需要进入 Survivor 或后续晋升。若短时峰值把存活率推到 25%,同样分配速率会产生 150 MB/s 的存活压力,晋升与老年代增长会完全不同。
单位时间存活量 ≈ 分配速率 × Young GC 后存活率
堆余量 = 最大堆 - 稳态存活集 - 突发分配与 GC 所需余量
容器内还要预留元空间、代码缓存、线程栈、直接内存、GC 数据结构和 JVM 本身开销。容器限制 8 GB 时,把 -Xmx 直接设为 8 GB,进程可能尚未堆 OOM 就被系统 OOM Killer 终止。
六、调优结果怎样验收
| 指标 | 含义 | 不能忽略的搭配项 |
|---|---|---|
| 吞吐量 | 应用时间占总时间比例 | CPU 使用率、处理量 |
| 暂停 P99 | 尾部延迟影响 | 业务接口 P99/P999 |
| GC 后占用 | 存活集基线 | 是否随时间持续上升 |
| 分配/晋升速率 | 内存压力来源 | 流量和对象生命周期 |
| Full/退化次数 | 兜底路径频率 | 触发原因和回收效果 |
MaxGCPauseMillis 是 G1 等收集器的目标提示,不是 SLA 保证。减小目标可能迫使收集器更频繁工作并损失吞吐;增大堆可能降低频率,却增加最坏回收成本。任何改动都应在相同流量模型下做 A/B 对比,并保留回滚方案。
JVM 调优的对象不是某个参数,而是“业务延迟、吞吐、资源成本和稳定性”的整体平衡。
调优报告还应记录 JDK 完整版本、收集器、启动参数和硬件环境,确保结果可复现。
七、常见误区与追问
- 误区:堆越大,GC 性能一定越好。 大堆可降低频率,但会提高内存成本,并可能增加扫描、回收和故障转储成本。
- 误区:把
Xms和Xmx设相同在任何场景都最优。 它可减少扩容抖动,但也会提前占用或提交资源,必须结合容器和部署密度评估。 - 误区:设置暂停目标就获得硬实时保证。 常见 JVM 收集器只把它当软目标,极端负载仍可能超出。
- 追问:为什么不能只看平均暂停? 少量长暂停会被平均值稀释,却可能直接击穿接口超时,应看 P95/P99/P999。
- 追问:Full GC 后占用不降说明什么? 可能是合理存活集过大或存在泄漏,需要结合对象保留链,而非立刻调堆。
- 追问:调大年轻代有什么副作用? 可能降低频率,但增加单次 Young GC 工作量,并压缩老年代可用空间。
- 追问:容器调优为什么要看堆外? 进程总内存还包含元空间、线程栈、直接内存、代码缓存和 GC 本身结构。
八、加强记忆
调优可以记成“目标—基线—假设—单改—验证—回滚”六步,第一步永远不是背参数。容量模型要把分配速率、Young GC 后存活率、稳态存活集与突发余量串起来,同时给线程栈、元空间、直接内存和 JVM 自身结构留预算。暂停目标通常是软目标,堆变大、年轻代变大或并发度提高都会在频率、单次成本、吞吐和 CPU 之间产生交换。验收必须同时看业务吞吐、暂停 P99/P999、GC 后基线、晋升速率与进程总内存,并保留可复现环境和回滚数据。