OpenJDK 主仓库 5 月 11 日收到编号 #31120 的拉取请求,标题是“实现 JEP 401:值对象(预览)”。这份 PR 同时捆绑了 JEP 539“JVM 严格字段初始化”,是 Project Valhalla 十多年演进后第一次进入官方 master pull request 流程。但它目前只通过了自动预检查,还没合并进 jdk/master。

值对象放弃了 record 保留的对象身份语义,这是这次改动真正的技术分野。高性能团队现在可以评估潜在的内存收益,框架和工具维护者需要先排查兼容性风险,但都还不到迁移生产系统的阶段。

谁在审这份 PR,为什么要捆绑 JEP 539

提交者 David Simms 在说明里写明:这份 PR 同时包含 JEP 539 的实现。原因很直接——值对象放弃身份语义后,必须靠更严格的初始化规则堵住并发场景的漏洞,两个 JEP 因此绑在一起提交。

语言、JVM、标准库三个子评审(issue 8317277、8317278、8317279)各自讨论,最后汇总进这一份 PR。参与审核和署名的工程师超过 60 人,包括长期负责 JVM 与语言规范的 Coleen Phillimore、Ioi Lam、Maurizio Cimadamore。

值对象和 record 差在哪

很多开发者第一反应:这不就是 record 换个名字?不是。

record 是引用对象,保留身份:能做同步锁,能用 == 比较引用,能作为 WeakHashMap 的 key。值对象放弃了身份——两个字段相同的值对象在语言层面直接等价,JVM 因此可能把它们当扁平数据处理,不必强制走对象头和堆分配。

特性record值对象(JEP 401 预览)
对象身份保留放弃
可否同步锁可以不可以
== 语义比较引用比较值
存储方式堆上引用分配有机会扁平化存储
当前状态Java 16 起正式可用JEP 401,首个预览阶段

这张表里最容易被忽略的一格是“可否同步锁”。value class 不能作 synchronized 的锁对象,现有代码里如果把 record 或普通对象当锁用,换成值对象会直接编译失败或运行时报错。这是最先会暴露出来的兼容性问题,不是理论风险。

值对象理论上能省掉部分对象头和堆分配开销,高性能团队可以把金融计算、坐标点、时间戳包装类这类场景列为潜在受益对象。但目前没有官方数字说明能省多少内存或提速多少,评估只能停在“机制已经搭起来”这一步。

通过预检查不等于进 master,现在该做什么

PR 页面上有一句容易被忽略:这份改动“通过了所有自动化预集成检查”。但提交者不具备 Committer 身份,必须找一位现有 Committer 做 sponsor 才能真正合并。评论区随后出现合并冲突提示——子评审 PR 与主仓库最新代码冲突,需要重新拉取合并。

阶段状态
提交 PR已完成,5 月 11 日
通过自动预检查已完成
找 Committer sponsor进行中
解决合并冲突进行中
合入 jdk/master尚未发生
落地某个正式 JDK 版本未知,不应预判

这张表说明一件事:现在下判断为时太早。目前唯一确定的是 PR 通过了预检查,其余每一步都还悬着。

框架和工具维护者现在能做、也该做的是排查代码里对“对象都有身份”的隐性假设:反射调用里有没有依赖 identityHashCode、序列化逻辑有没有假设对象经过 readObject 后仍是同一个引用、字节码处理工具有没有把 == 当身份比较用。这些假设在值对象落地后最先失效,值得先在测试环境里跑一遍,而不是等正式版发布再补救。

普通 Java 开发者眼下不需要改代码。合理的做法是留意后续 JDK 版本发布说明,确认这个预览特性挂在哪个版本号下面,再决定要不要加 --enable-preview 试一下——JEP 预览特性照例都要求加这个编译和运行参数,正式版才会去掉限制。