登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

JDK 26 G1 高吞吐优化怎样观察收益

来源:17golang原创

时间:2026-10-21 20:48:55 181浏览 收藏

如果一套 Java 服务从 JDK 25 升到 JDK 26 后,业务吞吐有变化,先别急着把变化归因于“G1 变快了”。JDK 26 的关键变化是 JEP 522:G1 通过减少应用线程与 GC 后台线程之间的同步,降低写屏障成本。它更可能帮助频繁修改对象引用的负载,收益仍然要用同一套输入、同一堆配置和同一组指标来观察。

官方地址:https://openjdk.org/projects/jdk/26/

最稳妥的判断方式是把 JDK 25 作为基线,把 JDK 26 作为候选,只改变运行时版本;同时记录业务吞吐、暂停分位数、Full GC、分配速率和 JVM CPU。单看平均暂停时间,无法证明 G1 高吞吐优化已经兑现。

JDK 26 的 G1 优化到底改了什么

G1 需要知道应用线程修改了哪些堆区域,才能维护跨 Region 的引用信息。旧设计里,应用线程和 G1 的后台优化线程会围绕同一套 Card Table 协作,写屏障需要承担更多同步与队列处理成本。

JEP 522 引入第二张 Card Table:应用线程写入当前活跃表,后台优化线程处理另一张表,双方不再频繁争用同一份工作区。需要交换工作时,G1 交换两张表的角色。这个变化不要求业务代码改 API,也不要求新增启动参数;它改变的是 G1 内部的同步路径。

JDK 26 G1 双 Card Table、写屏障与 Remembered Set 的静态结构关系图
图1:JEP 522 的结构示意图,展示应用写入边界与 G1 后台处理边界;这是静态说明图,不是运行截图或性能证据。

官方资料给出的方向是:引用字段写入频繁的应用更容易受益,Inside Java 对测试结果的概括为吞吐提升约 5% 到 15%;这应当被当作特定负载下的观察范围,而不是你的服务升级后的承诺值。

先固定基线,再谈“收益”

实际对比时,最容易犯的错是只替换 JDK,却同时更换容器规格、堆上限、压测数据或并发度。G1 的自适应行为会把这些变化放大,所以建议先固定以下条件:

  • 同一台机器或同一类容器规格,固定 CPU 配额、内存上限和 NUMA 条件;
  • 相同的 -Xms、-Xmx、业务线程数、输入数据和预热时间;
  • 同一版本的应用、依赖、JIT 预热流程和压测客户端;
  • 至少保留多轮结果,分别记录稳定运行阶段与刚启动阶段。

对 G1 来说,先从默认配置开始更容易解释结果。Oracle 的 JDK 26 调优指南建议不要用固定年轻代参数限制 G1 的调节空间;如果目标偏向吞吐,可以在确认暂停预算允许时放宽暂停目标,或提供更大的堆,但这些调整必须和 JDK 版本对比分开记录。

用 Unified GC Logging 留下可复查证据

下面是一种适合基线和候选版本都使用的启动写法。它只负责记录,不会替你判断收益;文件名带进程启动时间,轮转参数用于避免单个日志无限增长。

# 两个 JDK 版本使用完全相同的日志口径,便于横向比较
java \
  -Xms4g -Xmx4g \
  -XX:+UseG1GC \
  '-Xlog:gc*,safepoint=debug:file=logs/gc-%t.log:time,uptime,level,tags:filecount=5,filesize=50M' \
  -jar app.jar

第一轮不要急着把所有标签都压缩掉。Oracle 文档建议可以从 -Xlog:gc*=debug 开始,再依据问题收窄;如果要看暂停期间 CPU 的用户态、内核态和实际耗时,可以关注 gc+cpu=info。这样才能区分“GC 本身变少了”和“机器因为 CPU 或 I/O 被拖慢了”。

观察时至少保留这些字段:

  • Young、Mixed、Full GC 的次数与暂停时长;
  • 暂停时的堆前后占用,以及是否出现 Evacuation Failure;
  • 应用分配速率、老年代占用和 Humongous regions;
  • GC 线程 CPU、进程 CPU 与业务线程的 CPU 占比。

把 GC 数据和业务吞吐放在同一张表

建议每个版本产出一行摘要,避免只截一段日志。业务吞吐可以是稳定窗口内的请求数、批处理条数或消息消费量;暂停指标则同时保留平均值和 P95、P99。

# 这是记录模板,不是某次真实运行的输出
版本 | 稳定窗口 | 业务吞吐 | GC 总暂停 | Pause P95 | Pause P99 | Full GC | 分配速率 | JVM CPU
JDK 25 | 30 min | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填
JDK 26 | 30 min | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填

如果 JDK 26 的业务吞吐上升、暂停分位数不恶化,且 JVM CPU 没有因为别的配置变化而上涨,这才是比较完整的正向信号。如果吞吐不变但暂停略短,也可能是收益;如果只有一次峰值变好而稳定窗口没有变化,就先把它归类为噪声。

JDK 25 与 JDK 26 通过统一 GC 日志、JFR 和业务吞吐进行对比的静态指标关系图
图2:把版本、Unified GC Log、JFR 与业务指标放在同一观察边界内;这是静态关系图,不代表已经测得某组数值。

JFR 用来解释“为什么变了”

GC 日志适合看收集周期和暂停细节,JFR 更适合把 JVM 活动与应用行为放进同一个时间窗口。可以在压测稳定阶段开启一段短录制,重点是让 JDK 25 和 JDK 26 使用同样的录制参数。

# 在目标进程上录制一段固定时长的 profile 事件
jcmd  JFR.start name=g1-compare settings=profile duration=10m filename=records/g1-compare.jfr
# 录制结束后停止同名任务,避免长期占用磁盘
jcmd  JFR.stop name=g1-compare

JFR 不是为了替代业务监控。它可以帮助你解释 CPU 栈、分配热点和 GC 活动是否与吞吐变化同时出现,但最终上线判断仍要回到业务 SLO:请求成功率、延迟分位数、批处理完成时间和错误率。

哪些结果不能直接归功于 JDK 26

有几类结果很容易被误读:

  • 只看 Full GC 次数。 JEP 522 主要减少写屏障同步成本,不等于应用分配行为、堆大小和老年代压力都会改变。
  • 只看平均暂停。 平均值可能掩盖 P99 长尾,尤其是服务对尾延迟敏感时。
  • 把扩大堆带来的收益算到 JDK。 更大的堆会给并发标记更多时间,必须和运行时版本变化分开实验。
  • 直接套用固定百分比。 引用字段修改很少的服务,收益可能很小;即使同类服务,也会受 CPU、分配率和对象存活率影响。

如果服务出现 Full GC,可以继续按 G1 调优指南检查老年代占用、并发标记是否及时完成、Humongous regions 是否过多,以及是否存在显式 System.gc()。这些是独立的堆压力问题,不要用 JEP 522 代替常规排障。

最后用四个问题做上线判断

  1. 业务吞吐是否在多轮相同输入中稳定改善,而不是只有单轮峰值?
  2. Pause P95/P99、Full GC 和 Evacuation Failure 是否保持在原有预算内?
  3. JVM CPU、堆占用、分配速率和日志 I/O 是否没有引入新的代价?
  4. 如果收益不明显,是否仍然能保留 JDK 26 的其他价值,并有清晰的回滚版本?

满足这些条件时,可以把 JDK 26 当作一个有证据支持的候选版本推进灰度;不满足时,先保留日志和 JFR 记录,继续定位负载特征。G1 的吞吐改进是运行时内部优化,观察方法比“升级后一定更快”的结论更重要。

相关问题

JDK 26 需要新增 G1 参数吗?

JEP 522 的核心收益不要求业务代码或新参数配合。建议先保留默认 G1,再用统一基线验证;只有明确的暂停或堆压力问题,才进入单独的参数实验。

为什么吞吐提高了,暂停却没有明显变化?

写屏障开销属于应用线程路径,暂停时间还受收集集合、对象存活率、堆大小和机器 CPU 影响。两个指标观察的是不同层面,不能要求它们同步变化。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>