← 返回题目列表

JVM 调优常用参数有哪些?

困难 第 34 / 34 题 更新于 2026/07/25
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 分析。

完整版教学

一、调优的正确姿势:先定目标,再看数据,最后调参

这道题面试官真正想听的不是背参数,而是你有没有调优的方法论。参数谁都能查,思路才是水平:

  1. 明确目标:你要的是高吞吐(批处理、离线计算,追求单位时间处理量)还是低延迟(在线服务,追求短 STW)?两者常常互斥,目标不同调法完全不同。
  2. 建立监控:先有 GC 日志、堆使用曲线、STW 时长、各代占用等数据,看清楚瓶颈在哪。
  3. 针对性调整:根据数据动一两个参数,然后再压测对比,验证是否真的变好。
  4. 迭代:一次只改一个变量,否则无法判断是哪个改动起了作用。

记忆点:没有监控数据的调优就是玄学。「狂调参数碰运气」是最典型的错误。

二、吞吐 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 性能一定越好。 大堆可降低频率,但会提高内存成本,并可能增加扫描、回收和故障转储成本。
  • 误区:把 XmsXmx 设相同在任何场景都最优。 它可减少扩容抖动,但也会提前占用或提交资源,必须结合容器和部署密度评估。
  • 误区:设置暂停目标就获得硬实时保证。 常见 JVM 收集器只把它当软目标,极端负载仍可能超出。
  • 追问:为什么不能只看平均暂停? 少量长暂停会被平均值稀释,却可能直接击穿接口超时,应看 P95/P99/P999。
  • 追问:Full GC 后占用不降说明什么? 可能是合理存活集过大或存在泄漏,需要结合对象保留链,而非立刻调堆。
  • 追问:调大年轻代有什么副作用? 可能降低频率,但增加单次 Young GC 工作量,并压缩老年代可用空间。
  • 追问:容器调优为什么要看堆外? 进程总内存还包含元空间、线程栈、直接内存、代码缓存和 GC 本身结构。

八、加强记忆

调优可以记成“目标—基线—假设—单改—验证—回滚”六步,第一步永远不是背参数。容量模型要把分配速率、Young GC 后存活率、稳态存活集与突发余量串起来,同时给线程栈、元空间、直接内存和 JVM 自身结构留预算。暂停目标通常是软目标,堆变大、年轻代变大或并发度提高都会在频率、单次成本、吞吐和 CPU 之间产生交换。验收必须同时看业务吞吐、暂停 P99/P999、GC 后基线、晋升速率与进程总内存,并保留可复现环境和回滚数据。