开发者生态
evening
Valhalla 项目解释:十年的工作成果如何融入 JDK 28
2026-06-19
1 阅读
philonoist
Valhalla 项目,解释:JDK 28 十年的工作成果 - JVM Weekly vol. 180 新的 JVM Weekly 来了……Ragnarok 似乎也来了,因为我们终于在 JDK 中拥有了 Valhalla。然而,情况有点……微妙。 Artur Skowronski 2026 年 6 月 18 日 14 1 分享 6 月 15 日,Oracle 工程师 Lois Foltan 证实了业界大部分人不再相信的事情:JEP 401:值类和对象将集成到主 OpenJDK 存储库中,并且面向 JDK 28。感谢您阅读 JVM Weekly!免费订阅以接收新帖子并支持我的工作。更改如此之大,以至于剩余的提交者被要求在集成期间推迟更大的提交。仅 Pull 请求就在 1,816 个文件中添加了超过 197,000 行代码。不过,在我们开香槟之前:这是预览版,默认情况下禁用,并且,正如布莱恩·戈茨(Brian Goetz)很快让大家冷静下来一样,“这只是《瓦尔哈拉》的第一部分。” Goetz 补充了一个很好的观察,即“他们永远不会发货”的人群现在将顺利地切换到“但他们没有发货最重要的部分”(社区中多年来一直流传着一个笑话,我们自己最终会比项目发货更快地进入瓦尔哈拉,挪威来世的那个)。你必须赢得自己的仇恨。所以现在是讲述整个故事的好时机。本期是一次大的深度探讨,假设您以前从未关注过 Valhalla 的工作:从 2014 年的问题开始,通过想法的演变(其中相当多的想法最终被扔进了垃圾箱),一直到我们将在 JDK 28 中实际接触到的内容。给自己冲一杯咖啡。我已经关注这个版本很长时间了,就是为了这个场合才看的。 1. 简介 - 这到底是什么 Valhalla 从一开始就秉承的口号是:“像类一样编码,像 int 一样工作。”它用一句话概括了该项目的全部要点:我们希望编写带有方法、构造函数验证和合理字段名称的正常、可读的类,但我们希望 JVM 能够像基元一样有效地处理它们。要理解为什么这是一个问题,你必须回到 Java 的基础。在这种语言中,除了八个原语(int、long、double、boolean 等)之外,一切都是引用类型。当你写 Point p = new Point(1, 2) 时,变量 p 不是一个点。变量 p 是一个指针,一个衣帽号码:堆上的某个地方有一个对象,而你拿着一张写有它的地址的纸条。每次你想要读取一个字段时,JVM 都必须“进行外套检查”,通过指针执行跳跃(指针间接寻址)。对于单个对象来说,这没什么。问题始于规模。堆上的每个对象都有自己的标头(大约十几个字节的元数据:除其他外,因此 JVM 知道它是什么类型以及是否有人正在同步它)。顺便说一句,这正是 Project Lilliput 最近正在解决的问题,有助于缩小对象标头的大小。但标头大小并不是一切。每个对象都必须被分配,然后被垃圾收集。由于对象分散在堆中,一百万个点的数组实际上是一百万张纸条,指向散布在整个仓库中的一百万个盒子。 Brian Goetz 在他的“State of Valhalla”文档中称这种内存布局“蓬松”:膨胀、臃肿。我们梦想的是一种密集的布局,数据并排放置。为什么密度很重要?因为硬件的变化比Java更快。 1995 年,一次内存访问的成本与一次 CPU 操作的成本大致相同。如今,CPU 比主内存快两个数量级,并且整个差距由缓存弥补。处理器以称为高速缓存行(通常为 64 字节)的块的形式读取内存。如果数据密集且有序,那么一个这样的块会立即带来大量有用的值。如果我们跨越指针进行跳跃,则每次访问都会面临缓存未命中的风险,这可能比命中慢一百倍。这是参考局部性,也是整个游戏的真正重点。 “但是 JVM 有逃逸分析,”一些敏锐的人会说。正确:虚拟机可以识别出某些对象永远不会“逃逸”到本地代码片段之外,然后它根本不会分配它。从程序员的角度来看,对象看起来好像存在,但实际上它的字段分散到普通变量或 CPU 寄存器中。在最好的情况下,分配成本和垃圾收集器随后的清理成本几乎降至零。问题是这种优化是不可预测且脆弱的。仅当 JIT 编译器能够高度可信地跟踪对象的整个流程时,它才起作用。但所需要的只是对象落在另一个类的字段中,存储在数组中,传递到更复杂的方法中,或者出现在 JIT 可以分析的代码边界之外,整个技巧就会停止工作