JJava 知识库
JAVA INTERVIEW

高频面试题

JVM高级约 3 分钟

接口 RT 出现周期性尖刺,确认和 GC 有关,你会如何建立基线并逐步调优?

参考回答约 3 分钟 · 口语表达
先说结论

先说结论:GC 调优先确认问题确实来自 GC,再用暂停分位数、频率、吞吐损失、分配速率、晋升量和回收后占用建立基线。优先减少不必要分配和内存滞留,然后才调整堆、收集器和停顿目标,每次只改有限变量并压测验证。 低延迟、批处理吞吐和小容器实例的目标不同,不存在适用于所有应用的一组“最佳 JVM 参数”。

01

我先给结论,再说明它在项目里解决什么问题。以业务目标、GC 日志和运行指标驱动收集器与堆参数调优。GC 调优先确认问题确实来自 GC,再用暂停分位数、频率、吞吐损失、分配速率、晋升量和回收后占用建立基线。优先减少不必要分配和内存滞留,然后才调整堆、收集器和停顿目标,每次只改有限变量并压测验证。 低延迟、批处理吞吐和小容器实例的目标不同,不存在适用于所有应用的一组“最佳 JVM 参数”。

02

核心机制我会按一次真实执行过程来讲。沿着「定义吞吐与停顿 SLO → 采集稳定基线 → 定位分配和存活模式 → 一次调整一个变量 → 压测并灰度验证」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 定义吞吐与停顿 SLO 调优前先确定吞吐、P99 停顿、堆成本和容器限制,目标之间通常存在取舍。 采集稳定基线 至少采集完整 GC 日志、业务延迟、分配率和 GC 后占用,单次停顿样本不能代表基线。

03

实现细节只抓关键入口,不会整段背源码。GCeasy/GCViewer 或统一日志解析:停顿和代际基线。 jstat 仅应急采样:不能替代完整 GC 日志。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。

04

放到生产使用时,我会关注参数和验证数据。每轮只改变一个参数或一处分配代码,使用同数据同流量运行完整周期,对比业务 P99、CPU、吞吐与 GC 后基线。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「暂停 P99」为主基线,记录值应满足「<目标值」;同时保存 分配与晋升速率、停顿分位值,使后续变化能够回到同一时间轴比较。

05

最后补充常见误区和使用边界。暂停目标过于激进让 G1 使用更多 CPU、缩短年轻代并更频繁回收,应用吞吐下降。恢复合理目标并减少请求中的临时 JSON 对象后,停顿和吞吐同时改善。 只看 GC 次数不看停顿与业务影响:分配与晋升速率:一次只改一个变量。 一次修改多个参数无法归因:停顿分位值:调参前先做分配剖析。 把内存泄漏误当成堆太小:GC 后堆基线:灰度设置业务与 GC 双回滚线。 方案:更适合的场景:主要收益:代价与边界。 先优化分配:热点来自临时对象或批处理方式:根因收益持续且减少 GC 工作:需要代码与数据流改造。 调整堆与代际:存活集明确但空间或周期不合适:改动小、见效快:参数可能只适合当前负载。 更换收集器:SLO 与现有算法能力不匹配:获得不同停顿/吞吐特征:迁移验证和资源需求较大。 选型至少带上 堆与非堆容量、对象分配速率、存活率和停顿目标,并用上面的量化基线验证;