Project Valhalla 全面解读:十年磨一剑,JDK 28 终迎值类型
2026年6月15日,Oracle 工程师宣布了一个让整个行业等待已久的消息:JEP 401(值类与值对象)将被集成到 OpenJDK 主线仓库,目标为 JDK 28。这一变更规模之大,仅补丁本身跨越了 1,816 个文件,新增超过 19.7 万行代码。
不过要注意:这是一个预览特性,默认禁用,并且只是 Valhalla 的第一部分。让我们一起回顾这个项目的完整故事。
问题本质:"写起来像类,跑起来像 int"
Valhalla 项目从始至终的口号是:"codes like a class, works like an int"。在 Java 中,除了八种基本类型,一切都是引用类型。当你写 Point p = new Point(1, 2) 时,p 是一个指向堆上对象的指针。每次访问字段都需通过指针跳转。一百万个 Point 的数组在内存中实际是 100 万个指针指向散落在堆中的 100 万个对象,每个都有对象头开销。
这种"蓬松"的内存布局之所以是问题,是因为硬件变了。如今 CPU 比主存快两个数量级,差距由缓存弥补。如果数据紧密排列,一次缓存行读取就能带进大量有用值;如果在指针间跳跃,每次访问都可能缓存未命中,慢一百倍。
十年思想演变
Project Valhalla 始于 2014 年。团队建造了五个不同的原型,早期的"分离模型"让值类型与对象完全独立,结果淹没了整个 JVM 类型系统。突破来自 2019 年的"L World"原型:值类型与对象引用共享相同的"L"描述符,统一了模型。关键洞见是语言模型和 JVM 模型不需要百分百重叠。
命名也经历了多次演变:从"内联类"到"原始类"再到今天的"值类"。2021 年设想的双投影模型(Point.val/Point.ref)因为过于复杂最终被放弃。
JDK 28 中的值类模型
创建一个值类只需添加 value 修饰符:value class USDCurrency { ... }。所有实例字段隐式为 final,不能 synchronized,类默认为 final。决定性特性是没有 identity——两个内容相同的值对象被认为是相等的。== 运算符现在检查内容而非地址。
重要陷阱:值对象仍然可以为 null。非空限制被拆分到了单独的 JEP 中。
JVM 通过两种机制优化值对象:标量化将对象分解为其字段集,零分配;扁平化将数据直接嵌入数组或字段,实现密度和局部性。在传统模型中 Point[] 是指针数组,在值类模型中 JVM 可以直接在数组中存储值本身,一块连续内存,没有对象头,没有指针跳转。
关键在于没有牺牲抽象——Point 仍然是类,有构造函数、验证和方法。你得到了基本类型的密度同时保持了类的可读性。