Hacker News 中文摘要

RSS订阅

Project Valhalla 详解:十年之功,终抵 JDK 28 -- Project Valhalla, Explained: How a Decade of Work Arrives in JDK 28

文章摘要

Project Valhalla历经十年研发,其核心JEP 401(值类与对象)已确认将集成至OpenJDK主仓库,并计划随JDK 28发布。

文章总结

好的,这是根据您的要求,对原文主要内容进行的中文重述,保留了关键细节,并删减了与主题无关的评论性内容。


标题:Project Valhalla 详解:十年磨一剑,终抵 JDK 28

核心事件: 2026年6月15日,Oracle工程师Lois Foltan确认,JEP 401(值类与值对象)将被集成到OpenJDK主仓库,目标版本为JDK 28。此次变更规模巨大,涉及1816个文件,新增超过19.7万行代码。请注意,这目前是预览功能,默认关闭,且仅是Valhalla项目的第一部分。

Valhalla项目的核心目标: 项目的口号是“编码像类,运行像int”。目标是让开发者能编写具有方法、构造验证和字段名的普通类,同时JVM能像处理基本类型一样高效地处理它们。

问题的根源: 在Java中,除8种基本类型外,一切都是引用类型。例如,Point p = new Point(1, 2) 中的 p 是一个指向堆上对象的指针。每次访问字段都需要通过指针间接寻址。每个对象都有头部元数据,需要分配和垃圾回收。当对象在堆上分散时,数组(如Point[])实际上存储的是指向不同内存位置的指针,导致内存布局“蓬松”,数据局部性差。

为什么数据密度重要? 现代CPU速度远快于主存,性能瓶颈在于缓存。CPU以缓存行(通常64字节)为单位读取内存。如果数据在内存中紧密排列,一次读取就能加载多个有用数据。而通过指针跳转访问则容易导致缓存未命中,性能损失可达百倍。

现有解决方案的不足: 1. 逃逸分析:JVM可以识别不“逃逸”出局部代码的对象,并将其字段展开到寄存器中,避免堆分配。但这种优化不可预测且脆弱,一旦对象被存储到字段、数组或传递给复杂方法,优化就会失效。 2. 手动编码:放弃对象,用原始类型(如用三个byte表示颜色)手动编码数据。这虽然高效,但牺牲了代码的安全性、可读性和可维护性。

Valhalla的解决方案:值类与值对象 Valhalla旨在消除“要么用方便的类,要么用快速的基本类型”这种二元对立。

关键概念演变: 项目经历了多个原型阶段,最终确定了当前模型: 1. 早期“Q世界”:将值类型视为与对象完全不同的实体,导致类型系统复杂化。 2. “L世界”突破(约2019年):值类型与对象引用共享相同的“L描述符”,实现了统一。关键认识是:语言模型和JVM模型不必完全重叠。 3. 当前模型(JEP 401):引入值类(用 value 修饰符声明),其实例是值对象。值类仍然是引用类型,但没有身份。非空类型被分离到另一个可选的JEP中。

值类的具体特性: * 声明:使用 value 修饰符。所有实例字段隐式为 final,方法不能是 synchronized,类默认是 final。 * 核心特征:无身份。两个内容相同的值对象被视为相同,就像两个“4”没有区别。 * == 运算符的含义改变:对于值对象,== 比较的是可替换性(即递归比较所有字段),而非内存地址。 * 对值对象使用 synchronized 会抛出 IdentityException。 * 可为空:在JDK 28模型中,值对象仍然可以为 null。非空类型是未来的功能。

性能优化机制: 1. 标量化:JIT编译器将值对象的引用“分解”为其字段(如将Color对象分解为r, g, b三个字节),直接传递,无需堆分配。这比逃逸分析更可预测,适用范围更广。 2. 堆扁平化:将值对象的字段直接编码并写入到字段或数组单元中,而不是存储指向堆上另一个位置的指针。这实现了数据的紧密排列和良好的局部性。 * 限制:扁平化的数据需要原子读写,目前平台支持的最大原子操作是64位(包括空标志)。因此,字段总大小超过64位的值类可能无法扁平化。未来的128位编码和非空类型JEP将解决此问题。

对现有代码的影响: * 包装类:在预览模式下,IntegerLong 等包装类将成为值类,失去身份。JVM可以对其标量化和扁平化,使 Integer[] 的效率接近 int[],大幅减少装箱开销。 * 数组Color[] 数组可以直接存储扁平化的32位颜色编码,像 int[] 一样连续存储,处理器可以顺序高效地访问。

泛型与Valhalla的冲突及未来计划: Java泛型通过类型擦除实现,运行时 T 被擦除为 Object。这意味着值对象放入 List<Point> 时,会失去扁平化,被物化为堆上的普通对象。 修复计划分两阶段: 1. 通用泛型:语言层面的改变,允许类型变量覆盖值类型(如 List<int>),但仍通过擦除实现。 2. 特化泛型:未来的JVM扩展,将为具体类型参数生成特化的类布局,使 ArrayList<Point> 真正由扁平内存支持。JDK 28不包含此功能

总结:JDK 28带来了什么,没带来什么: * 已接受:JEP 401(值类与值对象)作为预览功能,目标JDK 28(2027年3月发布)。默认关闭,需启用 --enable-preview。 * 用户可获得:声明 value classvalue record;JDK中现有“基于值”的类(如Integer)迁移为值类;标量化和扁平化;更廉价的装箱。 * 不在JDK 28中:非空类型;完整的特化泛型;128位编码;完全成熟的JEP 402。 * 对生态的影响:为高性能Java领域(数据、向量计算、机器学习、游戏开发、金融等)提供了在不牺牲抽象的前提下获得密集数据布局的途径。开发者需注意 ==synchronized 在值类上的行为变化。

常见问题解答(FAQ)摘要: 1. 值类就是记录吗? 不是。record 放弃内部状态分离,value 放弃身份。两者可以组合。 2. 能用 == 比较值对象吗? 可以,但 == 现在比较的是可替换性(字段内容),而非地址。通常仍建议使用 equals。 3. 值类可以为 null 吗? 在JDK 28中,可以。非空类型是未来的JEP。 4. Integer变成值类会破坏代码吗? 大多数情况不会。主要影响是依赖身份的操作:== 会比较值,synchronized 会失效。 5. 会有快速扁平的 ArrayList<Point> 吗? 还没有。这需要未来的特化泛型。JDK 28中,扁平化直接作用于值类型的字段和数组(如 Point[])。 6. 这与C#的struct有何不同? C#的struct有身份和可变性,语义更重。Valhalla的值对象没有身份,内存布局由JVM决定,模型更简单,机器自由度更高。 7. 逃逸分析不是已经能做到这些了吗? 逃逸分析不可预测,且当对象“逃逸”时无效。值对象的标量化更可预测,适用范围更广。 8. 需要重写代码才能受益吗? 通常只需为表示“简单域值”且不依赖身份的类添加 value 修饰符。部分收益(如JDK自身类的迁移)是免费的。 9. 何时能看到完整的Valhalla? 未来版本。JDK 28是值类的第一个预览。完整功能(特化泛型、非空类型等)将分布在多个版本中,可能在下个LTS版本附近稳定。

评论总结

根据评论内容,总结如下:

主要观点与论据:

  1. 对Valhalla项目进展的认可与期待(评论2、9)

    • 评论2:作者赞赏团队将值类型设计整合为“始终像Java”的成果,并深入探讨了值类型的粒度与优化意义。
    • 评论9:认为Java在OpenJDK下正积极追赶,值类型是长期被忽视后的重要进步,强调“一次编写,到处运行”的愿景。
  2. 对设计决策的批评(评论3、6)

    • 评论3:批评团队以“心智负担”为由放弃null安全模型,认为“变量不可为空”的区分并不复杂,且可选安全保证不应被简化。
    • 评论6:指出值类型的==比较基于memcmp(),会暴露内部状态,破坏封装性,比身份比较更糟糕。
  3. 与.NET、Julia等语言的比较(评论4、10)

    • 评论4:质疑Java值类型与.NET structs的相似性,认为在值类型、泛型特化、装箱等方面选择相同。
    • 评论10:指出与Julia的不可变struct类似,数组存储值而非指针,但修改需创建新实例,略显冗长。
  4. 技术细节与性能疑问(评论5、7、8)

    • 评论5:质疑“无身份”是否必要;担心原子性限制(防撕裂)仅适用于小类型;认为堆分配可能连续,期待基准测试。
    • 评论7:对ArrayList<Point>尚未实现扁平化表示表示遗憾,希望下个LTS版本实现。
    • 评论8:纠正文章关于1995年内存访问速度的说法,指出当时缓存已比ALU慢,认为文章可能美化Java历史。

平衡性总结: - 支持者强调值类型对性能与内存布局的改进,以及Java在OpenJDK下的积极发展。 - 批评者关注设计简化牺牲了安全性与封装性,并质疑与现有语言(如.NET)的差异及性能提升的实际效果。 - 技术细节讨论聚焦于“无身份”必要性、原子性限制、堆分配连续性等,期待更多基准测试。