本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
今天,一大堆的公众号发了新年贺词。我看到的包括:新华社、交大、各大政企等,好不热闹。不就是过个元旦吗?
很多人以为明天就元旦了,又是新的一年,该有新的气象。而实际上,明天还是和今天一样,一事无成,工作一样做不完,天气一样糟糕,心情一样不好,身材一样胖,一样对未来没有期待。一切一切美好的期盼,都是自己欺骗自己的假象。 明天和今天,没什么不同,如果你今天不好过,那么明天你大概率一样不好过。
扯远了,还是回归正题吧。前段时间,我写到 JDK 26 的所有 JEP 特性都已经被冻结了,JDK 27 也已经开始构建了。在这其中,我发现 JEP 522 这个新特性,让 G1 GC 再次强大了,性能飙升,某些条件下提升了15% 左右。可以说是让吞吐量和低延迟这两个鱼和熊掌兼得了。那这里面有何秘密,接下来我们一起来解开这其中的神秘面纱。
G1 GC 遇上性能瓶颈
在 Java 应用性能优化的道路上,GC(垃圾回收)一直是开发者绕不开的话题。作为默认的 G1 GC,它一直在吞吐量和低延迟之间艰难平衡。直到 JEP 522 的出现,这个局面被彻底打破。
想象一下这样的场景:你的 Java 应用正在奋力处理海量请求,CPU 使用率居高不下,但性能监控显示大量时间消耗在了 GC 同步上。这不是你的代码有问题,而是 G1 GC 在背后默默“加班”。
OpenJDK 团队也注意到了这个问题,于是在JDK Enhancement Proposal 522中提出了一个巧妙的解决方案,通过减少应用线程与 GC 线程之间的同步开销,让 G1 GC 的吞吐量提升 5-15%。
接下来,我们一起来聊聊吞吐量 vs 低延迟,以及 JEP 522 如何让 G1 GC 性能飙升 15% 左右的。
吞吐量和低延迟能否兼得?
吞吐量和低延迟,就像鱼和熊掌一样,往往不能兼得。但它们之间还是有些优化空间的,尤其是 G1 GC。
接下来,我们先把吞吐量和低延迟,这两个核心概念搞清楚。
吞吐量(Throughput)
指单位时间内完成任务的数量。对于 GC 来说,吞吐量 = 应用程序运行时间 / (应用程序运行时间 + GC 暂停时间)。吞吐量越高,意味着 CPU 花在垃圾回收上的时间越少,处理业务逻辑的时间就越多。
说白了,吞吐量就像货车运输,吞吐量大意味着每次能运更多货物,往返次数更少。
低延迟(Low Latency)
指从请求到响应的时间尽可能短。对于 GC 来说,就是每次 GC 暂停的时间要足够短,不能让用户线程等待太久。
说白了,低延迟就像快递派送,延迟低意味着包裹能尽快送达,不会在中转站逗留。
真的不可兼得吗?
传统观念认为:吞吐量优先的 GC(如 Parallel GC)必然导致较长暂停时间;低延迟优先的 GC(如 CMS、ZGC)会牺牲部分吞吐量。
但 G1 GC 的设计初衷就是在这两者之间找到平衡点,但 JEP 522 之前,这个平衡并不完美,为了保证低延迟,G1 不得不引入复杂的同步机制,这反而拖累了吞吐量。
这个同步机制就是 JEP 522 需要改进的点。
G1 GC 的困境
其实上面已经说明白了 G1 GC 的困境,也就是为什么需要 JEP 522 的原因。接下来,我们稍微展开一下,说说这其中的核心问题:卡表扫描开销过大。
卡表扫描开销过大
G1 GC 通过“复制算法”管理内存。把存活对象从一个区域(Region)复制到另一个区域,然后回收旧区域。但问题在于,对象引用散落在堆各处,如何快速找到所有需要更新的引用?
G1 的解决方案是卡表(Card Table)。即:
- 每次应用线程修改对象引用字段时,写屏障(Write Barrier)会标记对应的卡表项
- GC 时只需扫描卡表,就能快速定位需要更新的引用
听起来很完美?但问题出在高并发场景下。
当应用频繁分配新对象并存储引用时,卡表会变得非常大,扫描它会超出暂停时间目标。
为了避免这个问题,G1 引入了优化线程(Optimizer Threads)在后台预处理卡表。但这就产生了新的问题:同步地狱。
- 优化线程需要扫描和压缩卡表
- 应用线程通过写屏障不断修改卡表
- 两者必须同步,否则数据会错乱
- 写屏障代码因此变得复杂,从 12 条指令膨胀到 50 条指令
双卡表设计
为了解决这个问题,JEP 522 的破局之道是双卡表设计。
OpenJDK 团队的解决方案堪称巧妙,即通过引入第二张卡表,让应用线程和优化线程各自为战。
架构变革
这就相当于从“共享一个笔记本”到“各自用草稿纸”。
对应的工作流程如下。
- 正常阶段:应用线程无锁更新卡表 A,优化线程处理卡表 B
- 切换触发:当 G1 判断扫描卡表 A 会超出暂停时间目标时
- 原子切换:通过 JEP 312 的线程本地握手机制,瞬间交换两个卡表
- 角色互换:应用线程继续无锁更新卡表 B,优化线程处理已满的卡表 A
- 循环往复:整个过程无需复杂同步
简图参考我的这篇文章《https://mp.weixin.qq.com/s/GrbThkS8fNrse3uFTd1mOw》。
从 50 条指令到 12 条
从 50 条指令到 12 条,是写屏障的瘦身。伪代码如下所示。
// 简化示意:改动前的写屏障(需要同步)
void write_barrier_before(Object obj, Object newRef) {
// 复杂同步逻辑...
lock(); // 获取锁
mark_card_table(obj); // 标记卡表
unlock(); // 释放锁
obj.field = newRef;
}
// 改动后的写屏障(无锁操作)
void write_barrier_after(Object obj, Object newRef) {
// 只需原子操作,无需锁
atomic_mark_card_table(obj);
obj.field = newRef;
}
改动前后指令数对比:
- x64 架构:50 条 → 12 条(减少 76%)
- ARM 架构:类似幅度的优化
性能提升,数据说话
JEP 522 带来的性能提升是实打实的。
吞吐量显著提升
- 重度引用修改场景:5-15% 的吞吐量提升
- 普通场景:最高 5% 的提升(得益于写屏障简化)
GC 暂停时间小幅下降
第二张卡表比之前的辅助数据结构更高效,GC 暂停时间略有降低。
内存占用权衡
- 新增第二张卡表占用:堆内存的 0.2%
- 1GB 堆 ≈ 额外 2MB 原生内存
- 好消息是这取代了之前更大的辅助结构,总体内存占用甚至减少
开发者什么都不用做!
JEP 522 的设计目标之一就是零配置、零迁移成本。
- 完全保持 G1 的现有架构
- 无需修改 JVM 参数
- 无需调整应用代码
- JDK 升级后自动生效(预计在 JDK 26)
唯一需要注意的是,如果你的应用对原生内存极度敏感,可能需要评估额外的 0.2% 左右的内存占用。
技术设计的启示
看到这里,想象 JEP 522 的成功给我们带来几点启发。
第一,空间换时间的经典再演绎。用额外的一张卡表,换取了应用线程和 GC 线程的解耦,这是典型的用空间换时间。
第二,无锁化(Lock-Free)的威力。减少同步是性能优化的永恒主题。无锁设计不仅提升性能,还降低了系统复杂度。
3. 向后兼容的重要性
OpenJDK 团队坚持“不破坏现有架构”,这让升级成本降到最低,有利于新特性的快速 adoption。
对比分析
下面是对比分析,其它 GC 何去何从?
| GC 类型 | 吞吐量 | 延迟 | 适用场景 | JEP 522 影响 |
|---|---|---|---|---|
| Serial | 高 | 极高 | 单线程/客户端 | 无 |
| Parallel | 最高 | 较高 | 批处理/科学计算 | 无 |
| G1 | 中高 | 低 | 通用/服务端 | +5-15% |
| ZGC | 中 | 极低(<10ms) | 超低延迟 | 无 |
| Shenandoah | 中 | 极低 | 超低延迟 | 无 |
简单来说,JEP 522 让 G1 GC 在保持低延迟优势的同时,吞吐量接近 Parallel GC,成为更均衡的选择。
是时候拥抱新 G1 了
JEP 522 通过巧妙的双卡表设计,解决了 G1 GC 长期以来的性能痛点。
- 吞吐量提升 5-15%
- 写屏障指令减少 76%
- GC 暂停时间略微下降
- 零迁移成本,升级即用
这对于广大 Java 开发者来说,也确实是让 Java 再次伟大。后续升级新的 JDK 版本,意味着:
- 服务端应用:可以获得免费的性能提升
- 微服务架构:延迟和吞吐量双优
- 云原生环境:资源利用率更高
参考资料
- JEP 522 官方文档:
https://openjdk.org/jeps/522 - G1 GC 官方文档:
https://openjdk.org/groups/hotspot-gc/ - 性能测试报告,OpenJDK 官方 Benchmark:
https://openbenchmarking.org/result/2206030-NE-JAVABENCH26

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » G1 GC指令从50行到12行,瘦身成功!Java 性能免费提升15%