本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
【腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
JDK 27 紧急刹车,回退了一个 P1 级 C2 编译器 bug!
还有不到 2 周,JDK 27 就要发布了。不过才发布更新的 JDK27-RC1 带来了一个“坏消息”,那就是官方团队紧急回退了一个 P1 级 Bug。这个 bug 编号为 JDK-8390590,属于 P1 级别,一个 C2 编译器 intrinsic 内存别名引起的 bug。这个 Bug 为什么是 P1 级别的呢?为什么要回退呢?下面我们一起来剖析一下吧。
JDK 27
根据 JDK 27 的发布计划,预计正式版本(GA)将于 2026 年 9 月 15 日发布,其中将包含 9 项功能,如下所示。
- JEP 523:在所有环境中将 G1 设为默认垃圾回收器
- JEP 527:TLS 1.3 后量子混合密钥交换
- JEP 531:惰性常量(第三个预览版)
- JEP 532:模式、instanceof 和 switch 中的基本类型(第五个预览版)
- JEP 533:结构化并发(第七个预览版)
- JEP 534:默认采用紧凑对象头
- JEP 536:JFR 进程内数据屏蔽
- JEP 537:向量 API(第十二个孵化版)
- JEP 538:加密对象 PEM 编码(第三个预览版)
除了这些,还修复和引入了一些 bug,其中 JDK-8390590 就是之一。
JDK-8390590
时间回到 2026 年 8 月 18 日,OpenJDK 核心编译器开发者 Vladimir Kozlov 提交了一个紧急修复请求,编号为 JDK-8390590,标题如下。
[BACKOUT] C2: Fix the memory around some intrinsics nodes
说白了,这是一个回退(Backout)操作。将此前一个名为 JDK-8373591 的改动整体撤销。原因是该改动在 JDK 27 的 EA(Early Access)版本中引入了一个新的回归缺陷 JDK-8390546,导致 C2 编译器在处理某些 intrinsic 方法时,使用了错误的内存别名(memory alias)。
这个 bug 被标记为 P1 优先级(最高优先级),并打上了 regression(回归)、jdk-rc-fix(发布候选修复)等标签,足见其严重性。修复随后被快速 backport 到了 JDK 27 的多个子版本(b35、b07、27.0.1、27.0.2)以及 JDK 28 的主线中。
文章配图参见我的公众号:https://mp.weixin.qq.com/s/TJeuwGxHGMADnRV2dnQpCw。
什么是 Intrinsic?
什么是 Intrinsic?内存别名为什么又如此关键?以及为什么要回退?我们接着往下看。
Intrinsic 是 JVM 的作弊器
在 HotSpot JVM 中,Intrinsic(内建函数)是一种特殊的编译器优化手段。当 JIT 编译器(尤其是 C2,即 Server Compiler)遇到某些特定的方法调用时,不会生成常规的 Java 字节码调用序列,而是直接替换为高度优化的机器码或特殊的 IR(中间表示)节点。
例如,下文涉及的 EncodeISOArray 就是 sun.nio.cs.ISO_8859_1$Encoder 类中的一个 intrinsic 方法,用于高效地将 char[] 编码为 ISO-8859-1 字节数组。类似的 intrinsic 还包括 System.arraycopy、Math.log、String.indexOf 等数百个。
C2 的内存模型与 Alias Analysis
C2 编译器在优化过程中,会构建一个复杂的内存依赖图(Memory Graph)。在这个图中,每个内存访问操作(Load、Store、ArrayCopy 等)都需要明确它访问的是哪一块内存区域。这种区域划分就是内存别名(Memory Alias)。
如果编译器错误地判断了两个内存操作指向的是同一块区域(即错误地判定它们 alias),就可能导致:
- 错误的指令重排:将本应顺序执行的内存操作并行化,破坏程序语义。
- 丢失的内存屏障:在多线程环境下,错误的别名分析可能导致可见性问题。
- 错误的死存储消除:编译器可能错误地认为某个 Store 操作是冗余的而将其删除。
对于 intrinsic 节点而言,它们通常涉及对数组的直接内存操作,因此正确声明其读写的内存别名是确保编译器优化安全性的前提。
根因分析
接下来,我们进行根因分析,看看 JDK-8373591 改了什么?又为何引发回归?
原始改动的初衷
#JDK-8373591 的标题同样是《C2: Fix the memory around some intrinsics nodes》,其目的是修复一些 intrinsic 节点在内存建模上的历史遗留问题。根据公开的邮件列表讨论,该改动主要解决了下面两类问题。
- AryEqNode 的 adr_type 错误:
AryEqNode(数组比较 intrinsic)继承了StrIntrinsicNode的TypeAryPtr::BYTES类型,但这并不正确,因为它实际上也可以接受char[]输入。 - StrInflatedCopyNode 等节点的反依赖缺失:某些 intrinsic 节点(如字符串膨胀复制节点)在消费内存时,没有正确声明它们”杀死”(kill)了哪些内存状态,导致调度阶段无法正确计算反依赖(anti-dependencies)。
该改动通过引入更精细的内存切片(memory slices)和 MergeMem 节点来修复这些问题,涉及 6 个文件、274 行代码的修改。
EncodeISOArray 的错误别名
然而,这个看似合理的修复却引入了新的问题。在 JDK 27 EA 的测试过程中,开发者发现 EncodeISOArray intrinsic 使用了错误的目标内存别名(incorrect destination memory alias)。
具体来说,EncodeISOArray 是一个将字符数组编码为字节数组的 intrinsic。它涉及到下面两个内存操作。
- 读取源
char[]数组 - 写入目标
byte[]数组
在 JDK-8373591 的改动后,该 intrinsic 节点在 C2 的 IR 图中对目标内存区域的声明出现了偏差。这导致编译器在后续的优化阶段(如全局代码移动、循环优化、标量替换等)中,无法正确识别该 intrinsic 与其他内存操作之间的依赖关系。
为什么这个错误如此危险?
错误的内存别名意味着 C2 可能会造成下面这些错误。
- 将 EncodeISOArray 与其他不相关的内存操作错误地重排,导致数据竞争或错误结果。
- 在循环优化中做出错误的假设,例如认为某个数组在循环体内未被修改,从而进行激进的向量化或展开。
- 影响逃逸分析和标量替换,因为编译器无法准确判断对象是否被 intrinsic 操作所引用。
由于这类错误往往只在特定的代码模式、特定的编译层级(C2 的 Tier 4)以及特定的数据流下才会触发,因此具有很强的隐蔽性,可能在生产环境中会以间歇性错误结果或难以复现的崩溃形式出现。
为什么选择回退而非打补丁?
如果是国人开发团队,业务给压力,那肯定是打补丁或当场修复的。
但老外的处理就很流程化,该回退就回退,绝不带着风险上线。
回退(Backout)策略
面对一个 P1 级的回归缺陷,且距离 JDK 27 的正式发布时间紧迫,OpenJDK 团队选择了最直接、最安全的策略,完整回退 JDK-8373591 的所有改动。
这种策略的优势总结下来有下面 3 条。
- 确定性高:回退操作本质上是撤销已知改动,风险可控。
- 修复速度快:从 bug 报告到修复提交(changeset
4b77534a)仅用了不到 10 小时。 - 避免引入更多回归:在紧张的发布周期内,对复杂编译器代码进行局部修补可能引发连锁反应。
Vladimir Kozlov 在修复请求中明确说明这是 “Clean backout”(干净的回退),并经过了 “A lot of testing”(大量测试)。
后续计划 REDO
回退并不意味着放弃。OpenJDK 团队随后创建了新的跟踪项 JDK-8390617,标题为 [REDO] C2: Fix the memory around some intrinsics nodes,计划在更充裕的时间窗口内,重新设计并实现对 intrinsic 节点内存模型的修复。
此外,团队还创建了 JDK-8390591 来添加针对本次回归的回归测试,确保类似问题不会再次发生。
最后
JDK-8390590 这起事件展现了大型开源项目中典型的“修复引发回归”场景。它时刻提醒着我们:
- 编译器优化是一把双刃剑:C2 的 intrinsic 机制为 Java 带来了卓越的性能,但其复杂性也意味着任何对内存模型的改动都必须经过极其严格的验证。
- 回退是一种智慧:在软件工程中,“敢于回退”往往比“硬撑着打补丁”更需要勇气,也更体现专业素养。
- 社区协作的力量:从 bug 报告(JDK-8390546)到回退决策(JDK-8390590),再到回归测试(JDK-8390591)和重新实现(JDK-8390617),整个流程在不到 24 小时内完成,体现了 OpenJDK 社区高效的协作机制。
对于我们普通 Java 开发者来说,新版本 JDK 它发任它发,用用 Java 8 或落后一点的 lts 版本也没什么不好。
参考链接
https://bugs.openjdk.org/browse/JDK-8390590https://bugs.openjdk.org/browse/JDK-8390546https://bugs.openjdk.org/browse/JDK-8373591https://bugs.openjdk.org/browse/JDK-8390617https://git.openjdk.org/jdkhttps://jdk.java.net/27/release-notes

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » JDK 27 紧急刹车,回退了一个 P1 级 C2 编译器 bug