Project Valhalla:Java 的性能跃迁
今天在看 Spring 的官方一篇文章时,看到下面一段描述。
翻译后:这种思维对 Project Valhalla 至关重要。Valhalla 旨在引入用户自定义的值类型(value types),使其行为如同基本类型般高效,同时保持表达力与安全性。Valhalla 将带来显著的性能提升,并推动 Vector API 等项目的发展,确保 Java 在与 C、Go 和 Rust 的竞争中始终立于不败之地。
出于好奇,我去检索学习了一下 Project Valhalla 的具体内容,看看是怎么做的,感觉很有收获,这篇文章因此而来。
前言
作为 Java 开发者,你一定写过下面这样的代码。
List<Integer> numbers = new ArrayList<>();
numbers.add(42); // 自动装箱
int first = numbers.get(0); // 自动拆箱
我们已习以为常,这里 42 是 int 类型,但是 ArrayList 只能存储对象,所以使用 Integer 进行包装。在 Java 中这个过程叫装箱(boxing)。
那么问题来了,这里有多余性能损耗。
- 每个
Integer对象除了存42值外,还要额外占用 12~16 字节的对象头。 - Integer 对象都分配在堆上,最后还得靠 GC 回收,增加 GC 压力。
- ArrayList 内部
Object[] data存储的元素都是引用地址,缓存命中率低,需要二次寻址。 - 如果要存一百万个整数,内存占用可能高达 28MB,而实际上只需要 4MB。
那能不能直接写 List<int>?不行。Java 编译器会直接报错。这是 Java 从诞生起就有的一个“硬伤”:基本类型不能参与泛型。
怎么办呢?默认接受了吗?
什么是 Project Valhalla?
示例代码中的问题,Oracle 和 OpenJDK 社区没有忘记,且已经给出解决方案在逐步实现。这个方案就是 Project Valhalla。
Valhalla 不是新语言,不是新语法,而是 Java 的一次真正的底层升级。它的目标很实在:让 Java 处理数据时更快、更省内存,同时代码还更好写。
简单说,Valhalla 要让 Java 支持一种新的“值类型”,让 List<int> 不仅合法,而且高效;让如 Point[] 之类的值对象数组可以真正连续存储,不再需要二次寻址,JVM 可能直接计算出 x 的值并返回,中间对象不需要了。
它主要干两件事:
- 引入值类型(Value Types):你可以定义像
Point、Money这样的类,它们在语义上是“值”(比如两个坐标相同的点就是同一个点),在性能上也如值类型int一样高效。 - 实现泛型特化(Generic Specialization):让
List<int>成为可能,告别装箱拆箱,内部直接存原始数据。
这些都不是语法糖,Valhalla 会修改 JVM 的字节码、内存布局、垃圾回收策略等,可以带来大幅度的性能提升。
为什么叫 “Valhalla”?
名字来自北欧神话中的“英灵殿”——英雄死后去的地方。OpenJDK 团队喜欢用神话命名大项目(比如 Loom、Panama),Valhalla 代表“让 Java 性能登峰造极”的愿景。
为什么 Java 以前不这样做?
历史原因:基本类型和对象的“割裂”
Java 1.0 为了简化内存管理,做了个妥协:
int、double这些基本类型直接操作,速度快、无身份,但是不能继承。- 其他一切(包括
String、List)都是引用对象,有身份、可继承,都是Object的子类,但处理起来速度会慢一点。
问题从这里开始, JDK 1.2 增加了如 ArrayList 类时,使用 Object 类型引用对象, 到 Java 5 引入泛型时,问题出现了,为了兼容老代码,只能选择“类型擦除”——编译后所有泛型都变成 Object。这就导致:
List<String> list = new ArrayList<>();
// 编译后其实还是 List<Object>
所以 List<int> 根本不可能,因为 int 不是 Object 的子类,只能使用包装类 Integer 代替,这里就不可避免的需要装箱拆箱。
性能代价:装箱、GC、缓存失效
装箱带来的问题不只是多几行字节码:
- 内存膨胀:一个
int占 4 字节,但Integer至少占 16 字节; - GC 压力大:大量临时
Integer对象让 GC 更忙; - 缓存不友好:数组里存的是指针引用,实际对象散落在堆各处,CPU 缓存经常失效。
很多高性能场景(比如游戏、金融)不得不自己写 IntList、DoubleArray 来绕过这个问题。这不仅很麻烦,也破坏了代码统一性。
终于,JVM 发展这么多年,社区达成了共识:是时候解决这个老问题了。
Valhalla 到底在做什么?
你可能有点好奇了,Valhalla 怎么做才能解决面对这些问题。
值类型:没有“身份”的类
Valhalla 引入了一种新语法值类型(目前还在预览阶段),它通过关键词 value class 定义。
value class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
这里的关键点:
- 值类型没有“对象身份”——两个
x=1, y=2的Point实例,在逻辑上完全等同, - 值类型没有“对象身份”,为了确保语义的一致性和 JVM 优化的可行性,字段必须是
final(不可变)的; - JVM 可以选择不把它当成普通对象,而是直接内联到其他对象或数组中。
这意味着你可以安全地用它表示坐标、颜色、金额等“纯数据”类型,不用担心性能问题。
泛型特化,List<int> 成为可能
Valhalla 的目标之一是支持像 List<int> 这样的语法,让泛型可以直接使用基本类型或值类型,从而避免装箱。但目前这一功能(称为‘泛型特化’)还在开发中,尚未在预览版 JDK 中可用。当前阶段,Valhalla 主要聚焦于值类型的定义与优化。
在泛型特化实现后,JVM 和标准库可能会为 List<int> 提供一个特化的实现,其内部使用 int[] 存储数据,从而避免装箱和间接寻址。这需要对集合类进行深度改造,并非简单语法糖。
或许怎么写还不知道,但可以肯定的是,可以没有装箱、拆箱。
同样,对于值类型:
List<Point> points = new ArrayList<>();
points.add(new Point(10, 20));
在内存中,points 的底层可能是一个连续的 [10, 20, ...] 数组,而不是 [ref0, ref1, ...] + 堆上分散的对象。
内存布局改造:从“指针跳转”到“连续存储”
当前 Java 中,Point[] 的内存布局是这样的:
数组: [ptr0] → 堆上: [x=10, y=20]
[ptr1] → 堆上: [x=30, y=40]
[ptr2] → 堆上: [x=50, y=60]
遍历时,CPU 要先读指针,再跳到堆上读数据,缓存经常失效。
Valhalla 之后,变成:
数组: [10, 20, 30, 40, 50, 60, ...]
连续内存 + 空间局部性 = 缓存命中率大幅提升。在循环密集的场景(比如物理引擎每帧更新上千个粒子),性能提升将会非常明显。
性能提升不止于集合
看了上面介绍,好像性能提升只体现在 ArrayList<Integer> 这类集合上,其实不是的。
对象字段也能“扁平化”
不光是数组,普通对象里的值类型字段也会被内联:
class Line {
Point start; // 不再是指针,而是直接嵌入 x, y
Point end;
}
内存布局从指针引用:
Line → [start_ref, end_ref] → [x,y] + [x,y]
变成:
Line → [x1, y1, x2, y2]
访问 start.x 和 end.x 可能在同一 CPU 缓存行,速度更快。
方法调用更轻量
小的值类型(比如 Complex 复数)可以完全通过 CPU 寄存器传递,不需要读写内存。JIT 编译器甚至能优化掉整个临时对象:
Point mid = midpoint(p1, p2); // 返回的 Point 可能根本不分配内存
return mid.x;
在这种情况下,JVM 可能直接计算出 x 的值并返回,中间对象“消失”了。
为未来的硬件优化铺路
连续的值类型数组更容易被 JVM 自动向量化(SIMD)。比如,HotSpot 未来可能用一条 AVX 指令同时处理 4 个 x 坐标。这在图像处理、机器学习推理中非常有用。
实测数据显示,在粒子模拟、金融定价等场景中,Valhalla 原型已实现 2~5 倍的吞吐提升,内存占用减少 70% 以上。
对开发者意味着什么?
直接的性能提升
JDK 非常重视向后兼容,因此老版本代码完全不受影响。JVM 也会在内部优化某些模式,什么都不修改也可带来性能提升。新代码可以逐步采用值类型。
代码更自然
手段多了,不用再纠结“用 Class 还是用 record 还是自己写”,以前你要表示一个坐标,可能会犹豫:
- 用
record Point(int x, int y)?但它是个普通对象,有身份,存进List需要指针引用。 - 自己实现如
IntList类?又怕性能不好。
有了 Valhalla 后,value class 就是为这类场景设计的:语义上是值,性能上也是值。和 record两者互补,覆盖不同场景。
JDK 原生支持,不需要第三方集合库
当前为了高效存 int,你可能要引用其他集合库,然后使用如 IntList 类:
// 当前做法
IntList list = new IntArrayList();
list.add(42);
Valhalla 后,标准 ArrayList 就够了:
// 未来做法
List<int> list = new ArrayList<>();
list.add(42);
生态统一,学习成本更低。
Valhalla 什么时候能用?
目前 value class 已进入 Preview 阶段。如果你想体验,可以在 jdk.java.net/valhalla 下载抢先体验的JDK 版本。
官方为此写了一篇文章:Try Out JEP 401 Value Classes and Objects
从文章中可以看到,仅“创建一个非常大的 LocalDate 值对象数组,并将它们的年份值相加” 这样的例子,在降低内存占用的同时,还带来 3 倍左右的速度提升。
总结
Project Valhalla 是为了解决 Java 真实存在的痛点:不能高效地处理一堆数字。是真正的底层革新。
它通过值类型和泛型特化,让 Java 在保留简单安全优势的同时,补齐了高性能数据处理的短板。从表面上看,它可能只是让 List<int> 变得符合语法,但对那些需要榨干每一滴性能的场景,它可能是 Java 能否继续胜任的关键。
最后想说。初学 Java 时,我们可能疑惑:“我想把 int 直接放进 List,为什么不行”?但是在知道装箱拆箱之后,就不再追问了,没想到这个问题社区一直在努力解决,而现在,快实现了。
Member discussion