Java基础、中级、高级、架构面试资料

G1 GC指令从50行到12行,瘦身成功!Java 性能免费提升15%

JAVA herman 14浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日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)在后台预处理卡表。但这就产生了新的问题:同步地狱

  1. 优化线程需要扫描和压缩卡表
  2. 应用线程通过写屏障不断修改卡表
  3. 两者必须同步,否则数据会错乱
  4. 写屏障代码因此变得复杂,从 12 条指令膨胀到 50 条指令

双卡表设计

为了解决这个问题,JEP 522 的破局之道是双卡表设计

OpenJDK 团队的解决方案堪称巧妙,即通过引入第二张卡表,让应用线程和优化线程各自为战

架构变革

这就相当于从“共享一个笔记本”到“各自用草稿纸”。

对应的工作流程如下。

  1. 正常阶段:应用线程无锁更新卡表 A,优化线程处理卡表 B
  2. 切换触发:当 G1 判断扫描卡表 A 会超出暂停时间目标时
  3. 原子切换:通过 JEP 312 的线程本地握手机制,瞬间交换两个卡表
  4. 角色互换:应用线程继续无锁更新卡表 B,优化线程处理已满的卡表 A
  5. 循环往复:整个过程无需复杂同步

简图参考我的这篇文章《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 带来的性能提升是实打实的。

吞吐量显著提升

https://openjdk.org/jeps/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%