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 中。这种转换可能听起来很奇怪;如果 Stringimmutable 的,它如何改变其状态?答案是从外部无法观察到这种改变;无论是否使用内部 String.hash 缓存字段,String 在功能上都会表现相同。唯一的区别是,随后的调用会更快。

现在我们知道了 String::hashCode 的工作方式,我们可以揭示所做的性能更改(由一行代码组成):内部字段 String.hash 标有 JDK 内部的 @Stable 注解。就是这样!

@Stable 告诉虚拟机,它可以读取该字段一次,如果它不再是其默认值(零),它可以信任该字段永远不会再次更改。因此,它可以 constant-fold String::hashcode 操作,并用已知的 hash 替换该调用。事实证明,不可变 Map 中的字段和 MethodHandle 的内部结构也以相同的方式被信任。这意味着虚拟机可以 constant-fold 整个操作链:

实际上,这意味着可以直接调用本地 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