JDK 21新特性简介

按照JDK每半年发布一次版本的节奏, JDK21在2023年9月19日发布,是一个长期支持版本, 本文就是简单介绍一下这次更新的新特性与改动.

一句话总结, 这仍然是jdk的一个短期支持版本,此版本包括7个 JEP(jdk增强建议),以及数百个较小的功能增强和数千个错误修复.

新特性汇总

语言特性:

  • JEP 440 Record Patterns(记录模式) : 记录模式是经过了前两个版本的孵化,这个版本正式发布了。其目的是将我们从java复杂的类构造中解放出来,更关注于数据. 一般用于一组数据构成的一个整体,比如坐标point(x,y)。记录模式可以嵌套,switch也支持记录模式。
  • JEP 4441 Pattern Matching for switch(switch记录模式): switch增强,支持记录模式等解析.
    • switch 支持null的判断,不用在外面判断一遍null了。当然为了兼容性,如果没有null case ,仍然会报空指针异常。
    • switch 支持when的语法支持,可以对case的子集进行二次匹配,返回布尔值即可
    • switch 支持类型自动推断(boolean 、 long 、 float 和 double等原始类型之外的类型)
    • switch 记录模式泛型的解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
final class C implements I {}

static void testSwitch(Object o) {
switch (o) {
case null -> { }
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s
when s.equalsIgnoreCase("YES") -> {
System.out.println("You got it");
}
case String s
when s.equalsIgnoreCase("NO") -> {
System.out.println("Shame");
}
case String s -> {
System.out.println("Sorry?");
}
case Pair<I>(I fst, C snd) p -> {
System.out.println("Sorry?");
}
}
}

库升级

  • JEP 444 Virtual Threads(虚线程) : 对标go语言协程的虚线程终于投入了使用,也就是说在一个系统线程上可以再开辟出平台级的虚线程来实现轻量级并发
    • 根据官方的建议,虚线程因为更轻量,所以不用搞什么虚线程池,随用随建即可
    • 虚线程与系统线程的关系是M:N
    • G1 GC 不支持巨大的栈块对象。如果一个虚拟线程的栈达到区域大小的一半,可能小至 512KB,那么可能会抛出 StackOverflowError
1
2
3
4
5
6
7
8
9
// 虚线程的调用
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
  • JEP 431 Sequenced Collections(有序集合) : 原先集合类接口是没有在顺序方面的相关接口,这些都是依赖于底层实现类实现的,现在这些顶层接口补上了这一功能,主要影响是collectionmap接口。
    • 新增SequencedCollection接口
1
2
3
4
5
6
7
8
9
10
11
12
13
interface SequencedMap<K,V> extends Map<K,V> {
interface SequencedCollection<E> extends Collection<E> {
// 新方法
SequencedCollection<E> reversed();
// 从 Deque 提取的方法
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
}
  • 新增SequencedMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SequencedMap<K,V> extends Map<K,V> {
// 新方法
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// 从 NavigableMap 提取的方法
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}

最终类图:
jdk21中新增的有序接口

  • JEP 452 Key Encapsulation Mechanism API(密钥封装机制) : 一种使用公钥密码学来保护对称密钥的加密技术,现在java把这套标准接口实现类。

性能提升

  • JEP 439 Generational ZGC(ZGC垃圾回收器) : 垃圾收集器(ZGC)扩展到维护年轻和旧对象的单独代,提高应用程序性能

其他新特性

  • JEP 451 Prepare to Disallow the Dynamic Loading of Agents(准备禁止动态加载Agents) : 动态加载(Attach API)可以在程序运行过程中,“热加载”一些功能(比如修复bug)到运行的程序中,这不安全,也不符合代码的完整性。所以这个版本会提示警告,并在未来将这个功能禁用。
  • JDK-8301226 新增Math.clamp() 和 StrictMath.clamp() 方法 : 这两个方法可以方便地将数值值限制在指定的最小值和最大值之间。
  • JDK-8302590 新增 String indexOf(int,int,int) 和 indexOf(String,int,int) 方法 : 添加了两个新方法 indexOf(int ch, int beginIndex, int endIndex) 和 indexOf(String str, int beginIndex, int endIndex) 到 java.lang.String 中,以支持对字符 ch 的前向搜索,以及分别对 String str 的搜索,并限制在指定的索引范围内。
  • JDK-8305486 新增 splitWithDelimiters 方法 : 与这些 split() 方法不同,在 java.lang.Stringjava.util.regex.Pattern 中新增的 splitWithDelimiters() 方法返回字符串和分隔符,而不仅仅是字符串。
  • JDK-8302323 StringBuilder和StringBuffer新增 repeat 方法 : sb.repeat(‘-‘, 80) 将将 80 个连字符插入到 java.lang.StringBuilder sb 对象的值中
  • JDK-8305107 支持Emoji的正则匹配 : 可以使用新的 \p{IsXXX} 构造来匹配具有 Emoji 相关属性的字符, 如:Pattern.compile("\\p{IsEmoji}").matcher("🉐").matches()
  • JDK-8191565 Last Resort G1 Full GC Moves Humongous Objects : G1垃圾回收时会移动巨型对象,这样可以保持尽量多的连续空间,方便后续巨型对象的分配。
  • JDK-8298127 新增 HSS/LMS 签名验证
  • JDK-8288050 PBES2 摘要算法新增 SHA-512/224 和 SHA-512/256 实现
  • JDK-8301553 新增基于密码的加密的算法SunPKCS11
  • JDK-8306560 JShell 里可以调用jdk工具 : 比如在JShell里调用jmap工具
  • JDK-8208077 File::listRoots 将 Windows 上可用的驱动器全部返回 : 在本次版本中,方法 java.io.File.listRoots() 在 Microsoft Windows 上的行为已更改,返回的数组现在包括所有可用磁盘驱动器的 File 对象。这与 JDK 10 到 JDK 20 中的行为不同,当时此方法会过滤掉不可访问或没有媒体的磁盘驱动器。此更改避免了在先前版本中观察到的性能问题,并确保该方法与 FileSystem.getDefault().getRootDirectories() 返回的根目录迭代保持一致。
  • JDK-8305092 Thread.sleep(millis, nanos) 现在能够执行亚毫秒级睡眠 : Thread.sleep(millis, nanos) 方法能够在 POSIX 平台上执行亚毫秒级睡眠。在此更改之前,非零 nanos 参数会向上取整到完整的毫秒。虽然大多数 POSIX 系统上的精度得到了提高,但实际的睡眠持续时间仍然受系统设施的精度和准确性的影响。

预览阶段特性:

语言特性

  • JEP 430 String Templates(字符串模板) : 对原来的str.format()能力进行了增强,使其可以更易读和更容易使用。
    • 比如:STR."\{x} + \{y} = \{x + y}"; 结果为:"10 + 20 = 30"
    • 支持数组:STR."\{array[0]} + \{array[1]} = \{array[0] + array[1]}";
    • 支持模板的嵌套使用:String tmp = STR."\{x}, \{y}"; String s = STR."\{z}, \{tmp}";
    • 跨行使用:
1
2
3
4
5
6
7
8
9
10
11
12
String title = "My Web Page";
String text = "Hello, world";
String html = STR."""
<html>
<head>
<title>\{title}</title>
</head>
<body>
<p>\{text}</p>
</body>
</html>
""";
  • 自定义字符串模板工具:FMTRAW ,其中FMT负责字符串模板的插值操作,RAW负责生成一个未经解析的StringTemplate对象。
1
2
3
4
5
6
7
String name = "Joan";
String info = STR."My name is \{name}";

// 等价于
String name = "Joan";
StringTemplate st = RAW."My name is \{name}";
String info = STR.process(st);
  • JEP 443 Unnamed Patterns and Variables(未命名模式) : 通过未命名模式增强 Java 语言,这些模式匹配记录组件而不声明组件的名称或类型,以及未命名变量,这些变量可以初始化但不能使用。两者均以下划线字符表示,即 _。
1
2
3
4
5
6
7
8
9
10
11
12
int acc = 0;
for (Order order : orders) {
if (acc < LIMIT) {
acc++;
}
}
// order 声明了但是没有使用,可以简化成下面形式
for (Order _ : orders) {
if (acc < LIMIT) {
acc++;
}
}
  • JEP 445 Unnamed Classes and Instance Main Methods(未命名主方法) : 简单来说,就是简化了main入口方法的声明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HelloWorld { 
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
// 可以写成实例主方法,而不再是一个静态方法:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}

// 甚至可以写成未命名类的形式:
void main() {
System.out.println("Hello, World!");
}

通过这种方式简化java主方法的声明,但这也带来了一个问题,当一个类里有多个main方法的时候,执行的是哪一个呢?jvm会选择下面的第一个来调用

  1. public static void main(String[] args)
  2. public static void main()
  3. void main(String[] args)
  4. void main()

库升级

  • JEP 442 Foreign Function & Memory API(外部函数和内存访问) : 第三次预览。java 程序可以通过它与 Java 运行时之外代码和数据交互。通过高效调用外部函数(即 JVM 之外的代码),以及安全访问外部内存(即不由 JVM 管理的内存),该 API 使 Java 程序能够在没有 JNI 的脆弱性和危险的情况下调用本地库和处理本地数据.
  • JEP 446 Scoped Values(作用域值) : 通过引入作用域值,使得一些信息可以在线程及其子线程间共享数据,类似ThreadLocal这样,跳过传参来进行值传递的方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final ScopedValue<String> X = ScopedValue.newInstance();

void foo() {
ScopedValue.where(X, "hello").run(() -> bar());
}

void bar() {
System.out.println(X.get()); // prints hello
ScopedValue.where(X, "goodbye").run(() -> baz());
System.out.println(X.get()); // prints hello
}

void baz() {
System.out.println(X.get()); // prints goodbye
}
  • JEP 448 Vector API(向量api) : 第六次预览。现代的CPU都提供了向量指令集,该 API 能够在支持的 CPU 架构上可靠地编译为最优向量指令,从而实现比等效标量计算更优越的性能。

移除的功能

  • JDK-8300977 java.io.File’s Canonical Path Cache Is RemovedFile::getCanonicalFileFile::getCanonicalPath的路径缓存功能被废除,对使用没有影响
  • JDK-8297295 ThreadGroup.allowThreadSuspension Is Removed : 本版本中已移除 java.lang.ThreadGroup.allowThreadSuspension(boolean) 方法。该方法用于 JDK 1.1 中的低内存处理,但从未完全指定。它在 JDK 1.2(1998 年)中被弃用,并改为“无操作”。
  • JDK-8205129 The java.lang.Compiler Class Has Been Removed :java.lang.Compiler 类已被移除。这个未指定充分 API 始于 JDK 1.0,早期 JDK 版本中使用的“经典 VM”。在 HotSpot VM 中的实现只是打印一条警告,指出它不受支持。该类自 Java SE 9 以来已被弃用并标记为删除。
  • JDK-8302819 Remove the JAR Index Feature :“JAR 索引”功能已被从 JAR 文件规范中删除。JAR 索引是早期 JDK 版本中的遗留优化,允许在通过网络加载小程序或其他类时推迟下载 JAR 文件。自 JDK 18 以来,该功能已被禁用,意味着 JAR 文件中的 META-INF/INDEX.LIST 条目在运行时将被忽略。
  • JDK-8307244 Remove the JAR Index Feature :该类 javax.management.remote.rmi.RMIIIOPServerImpl 已被移除。在 JDK 9 中,IIOP 传输已被从 JMX 远程 API 中移除。自 Java SE 9 以来,此类已被弃用,其构造函数已更改为抛出 UnsupportedOperationException 。
  • JDK-8225409 Removal of G1 Hot Card Cache :G1 热卡缓存已被移除。性能测试表明,在并发精炼控制的改进之后,它对性能没有贡献。而移除该功能后,减少了 G1 垃圾收集器的内存占用,大约为 Java 堆大小的 0.2%
  • JDK-8302385 The MetaspaceReclaimPolicy Flag has Been Obsoleted :MetaspaceReclaimPolicy 选项曾用于在类卸载后微调元空间的内存回收行为。实际上,这效果有限,很少使用。

废弃的功能(后来会移除)

  • JDK-8280031 Deprecate GTK2 for Removal : GTK2 用于linux上对 AWT/Swing 的实现支持,随着 GTK4 在 2020 年 12 月的发布,GTK 2 工具包即将达到其生命周期的终点
  • JDK-8303175 com.sun.nio.file.SensitivityWatchEventModifier Is Deprecated : 该枚举中的常量用于在 macOS 上基于轮询的 WatchService 实现中设置轮询文件更改的间隔。基于轮询的 WatchService 已更改,在注册要监视的文件时忽略这些修饰符。
  • JDK-8304982 Emit Warning for Removal of COMPAT Provider : COMPAT 在 JDK 9 时代提供,用于迁移到 CLDR 区域数据,当时它成为了默认的区域数据(JEP 252)。JDK 21 为了兼容性保留了 JDK 8 的遗留区域数据,但一些新功能并未应用。遗留的区域数据将在未来的版本中删除。鼓励用户迁移到 CLDR 区域数据
  • JDK-8298966 Deprecate JMX Subject Delegation and the JMXConnector.getMBeanServerConnection(Subject) Method for Removal : JMX Subject Delegation 功能已被废弃,并计划在未来版本中删除。

问题修复

  • JDK-8307990 Fixed Indefinite jspawnhelper Hangs : 修复了不定时 jspawnhelper 停滞问题

    自 JDK 13 以来,在 Linux 上执行子进程的命令默认使用所谓的 POSIX_SPAWN 启动机制(即 -Djdk.lang.Process.launchMechanism=POSIX_SPAWN )。在父 JVM 进程在 JVM 与新创建的 jspawnhelper 进程之间的握手完成之前异常终止的情况下, jspawnhelper 在 JDK 13 到 JDK 20 中可能会无限期地停滞。此问题已在 JDK 21 中修复。如果父进程有打开的套接字,则此问题尤其有害,因为在这种情况下,派生的 jspawnhelper 进程将继承它们并保持所有相应的端口打开,从而有效地阻止其他进程绑定到它们。

    这种异常行为已在内存约束严格的环境中频繁 fork 子进程的应用程序中观察到。在这种情况下,操作系统可能会在 fork 过程中终止 JVM,从而导致所述问题。如果新进程尝试绑定与初始应用程序相同的端口,则由于它们将被挂起的 jspawnhelper 子进程阻塞,因此无法在崩溃后重启 JVM 进程。

    此问题的根本原因是 jspawnhelper 未关闭用于与父 JVM 握手的管道的写入端,这已在尝试从父进程读取数据之前关闭了通信管道的写入端得到修复。这样, jspawnhelper 将可靠地从通信管道读取 EOF 事件,并在父进程意外死亡时终止。

    这种问题的第二个变体可能发生,因为 JDK 中的握手代码没有正确处理 write(2) 的中断。这可能导致向 jspawnhelper 子进程发送不完整的信息。结果是父线程和子进程之间的死锁,这表现为在读取管道时被阻塞的 jspawnhelper 进程,以及相应的父 Java 进程中的以下堆栈跟踪

  • JDK-8307466 Error Computing the Amount of Milli- and Microseconds between java.time.Instants : 使用 ChronoUnit.MILLIS.between(t1, t2)ChronoUnit.MICROS.between(t1, t2)t1.until(t2, MILLIS)t1.until(t2, MICROS) 计算两个时间点之间的时间差已被纠正。自 JDK 18 以来,计算实例之间单位的实现没有在计算毫秒和微秒时在秒和纳秒时进行进位和借位。

  • JDK-8303465 Enhance Contents (Trusted Certificate Entries) of macOS KeychainStore : macOS KeychainStore 实现现在在用户域、管理员域或两者中正确信任证书。在此之前,只有用户域被考虑。此外,如果证书的信任设置中在任一域存在针对特定目的的“拒绝”条目,则该证书将不会成为 macOS KeychainStore 的一部分.

  • JDK-8305091 Allow Key/Nonce Reuse for DECRYPT_MODE ChaCha20 and ChaCha20-Poly1305 Cipher Objects : SunJCE 对 Cipher 对象使用 ChaCha20 和 ChaCha20-Poly1305 算法的实现现在将在 DECRYPT_MODE 时允许密钥/nonce 重用。此更改使这些算法与当前 SunJCE AES-GCM 解密模式行为在密钥/nonce 重用方面保持一致。所有 ENCRYPT_MODE 密钥/nonce 重用禁令的行为保持不变。

  • JDK-8027682 Disallow Extra Semicolons Between “import” Statements : Java 语言规范不允许在 import 语句之间出现多余的分号,然而编译器之前允许这样做;JDK21 修复了这个问题。

jdk21 支持的工具

全平台可用的jdk 工具
全平台可用

仅windows可用的jdk工具
windows only

名词解析

  • HarfBuzz: 一个开源的用于文字塑形的软件开发库,亦即用于转换Unicode文本到字形指标及方位的过程

  • FreeType: 同HarfBuzz一样, 也是一个开源字体库. 它是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持

  • Preview(预览): 功能已经基本完整, 可以试用了. 可以简单理解为beta公测版. 来源于 JEP12

    功能以预览版的形式发布,以收集有关它们的反馈而不承诺保持其向后兼容性——这意味着鼓励每个人尝试它们,但同时不鼓励在生产中使用它们。

    预览功能不是开箱即用的,为了访问它们,需要使用*–enable-preview*编译器标志。

  • Incubator(孵化) : 实验性 API已经到了一定阶段, 已经计划开发出一整套完整的功能.以独立模块的形式发布. 来源于 JEP11

  • Experimental(实验) : vm级的早期功能, 不稳定, 功能不完整. 实验性质.

    实验性功能代表(主要是)VM 级功能的早期版本,这些功能可能是有风险的、不完整的,甚至是不稳定的。在大多数情况下,需要使用专用标志启用它们

    出于比较的目的,如果一个实验功能被认为是 25%“完成”,那么一个预览功能应该至少 95%“完成”。

    预览,孵化,实验三者的关系大致: 实验 => 孵化 => 预览 => 合并jdk主体功能.

  • JEP : JDK Enhancement Proposal , jdk增强建议. 也就是我们常说的jdk新特性的来源. JEP大全

往期文章

jdk19新特性

jdk20新特性

参考文章

jdk21官方文档
jdk21的新特性
jdk21及后续修复bug版本文档
jdk21垃圾回收器官网
jdk21虚拟机介绍

openjdk官网
jdk中预览,实验,孵化的关系
jdk各版本支持时间


JDK 21新特性简介
https://www.hancher.top/2025/04/21/java-jdk-v21/
作者
寒澈
发布于
2025年4月21日
更新于
2025年4月24日
许可协议