String 性能大提升
Inside Java
来自 Oracle Java 团队成员的新闻和观点 Newscast | Podcast | JEP Café | Sip of Java dev.java | Newsletter | About | Jobs 排序方式: Date | Author | Tag
String 性能大提升
Per-Ake Minborg 于 2025 年 5 月 1 日发布
在 JDK 25 中,我们改进了 String
类的性能,使得 String::hashCode
函数大部分情况下是 constant foldable。例如,如果您在静态不可变的 Map
中使用 Strings 作为键,您可能会看到显著的性能提升。
示例
这是一个相对高级的示例,我们维护一个不可变的本地调用 Map
,它的键是方法调用的名称,值是一个 MethodHandle
,可以用于调用相关的系统调用:
// Setup an immutable Map of system calls
static final Map<String,MethodHandle> SYSTEM_CALLS = Map.of(
“malloc”, linker.downcallHandle(mallocSymbol,…),
“free”, linker.downcallHandle(freeSymbol…),
...
);
...
// Allocate a memory region of 16 bytes long
address = SYSTEM_CALLS.get(“malloc”).invokeExact(16L);
...
// Free the memory region
SYSTEM_CALLS.get(“free”).invokeExact(address);
Copy
linker.downcallHandle(…)
方法接受一个 symbol 和其他参数,通过 JDK 22 中引入的 Foreign Function & Memory API 将本地调用绑定到一个 Java MethodHandle
。这是一个相对缓慢的过程,涉及 spinning bytecode。然而,一旦进入 Map
,仅 String
类中的新性能改进就允许对键查找和值进行 constant folding,从而将性能提高 8 倍以上:
---JDK24---
Benchmark Mode Cnt Score Error Units
StringHashCodeStatic.nonZero avgt 1 54.632 ± 0.042 ns/op
---JDK25---
Benchmark Mode Cnt Score Error Units
StringHashCodeStatic.nonZero avgt 1 50.571 ± 0.012 ns/op
Copy
注意:上面的基准测试没有使用 malloc()
MethodHandle
,而是使用了一个 int
identity function。毕竟,我们不是在测试 malloc()
的性能,而是在测试实际的 String
查找和 MethodHandle
性能。
此改进将使任何具有 Strings 作为键且值(任意类型 V
)通过常量 Strings 查找的不可变 Map<String,V>
受益。
它是如何工作的?
当第一次创建 String
时,它的 hashcode 是未知的。在第一次调用 String::hashCode
时,实际的 hashcode 会被计算并存储在一个私有字段 String.hash
中。这种转换可能听起来很奇怪;如果 String
是 immutable 的,它如何改变其状态?答案是从外部无法观察到这种改变;无论是否使用内部 String.hash
缓存字段,String
在功能上都会表现相同。唯一的区别是,随后的调用会更快。
现在我们知道了 String::hashCode
的工作方式,我们可以揭示所做的性能更改(由一行代码组成):内部字段 String.hash
标有 JDK 内部的 @Stable
注解。就是这样!
@Stable
告诉虚拟机,它可以读取该字段一次,如果它不再是其默认值(零),它可以信任该字段永远不会再次更改。因此,它可以 constant-fold String::hashcode
操作,并用已知的 hash
替换该调用。事实证明,不可变 Map
中的字段和 MethodHandle
的内部结构也以相同的方式被信任。这意味着虚拟机可以 constant-fold 整个操作链:
- 计算 String “malloc” 的哈希码(始终为
-1081483544
) - 探测不可变的
Map
(即,计算内部数组索引,对于malloc
哈希码始终相同) - 检索关联的
MethodHandle
(始终位于所述计算索引上) - 解析实际的本地调用(始终是本地
malloc()
调用)
实际上,这意味着可以直接调用本地 malloc()
方法调用,这解释了巨大的性能改进。换句话说,操作链被完全短路了。
有什么限制?
有一个不幸的角落案例,新的改进没有涵盖:如果 String
的哈希码恰好为零,则 constant folding 将不起作用。正如我们上面了解到的,constant folding 只能针对非默认值(即 int
字段的非零值)进行。但是,我们预计我们能够在不久的将来解决这个小障碍。您可能会认为只有大约 40 亿个不同的 Strings 中只有一个的哈希码为零,这在平均情况下可能是正确的。但是,最常见的字符串之一(空字符串“”)的哈希值为零。另一方面,没有 1 - 6 个字符(包括 1 和 6)的字符串(所有字符的范围从
(空格)到 Z
)的哈希码为零。
最后说明
由于 @Stable
注解仅适用于内部 JDK 代码,因此您无法直接在 Java 应用程序中使用它。但是,我们正在开发一个新的 JEP,称为 JEP 502: Stable Values (Preview),它将提供一些构造,允许用户代码以类似的方式间接受益于 @Stable
字段。
下一步是什么?
您可以立即下载 JDK 25,看看这种性能改进将使您当前的应用程序受益多少。
About This Site | OpenJDK Community | Download Java
© 2025 Oracle Corporation and/or its affiliates