JDK 21 LTS 版本核心特性介绍 – wiki基地


JDK 21 LTS 核心特性深度解析:Java 平台的又一次飞跃

自 Java 诞生以来,它一直是企业级应用开发和各种软件领域的主力军。随着技术的飞速发展,Java 平台也在不断演进,以适应新的编程范式、提升开发者生产力、优化运行时性能以及更好地集成现代硬件能力。在这一持续迭代的过程中,长期支持(LTS)版本扮演着至关重要的角色,它们为企业提供了稳定、可靠、具备长期维护周期的平台基础。

2023年9月19日,Oracle 正式发布了备受瞩目的 Java Development Kit (JDK) 21,这是一个新的 LTS 版本。JDK 21 集结了过去几个版本中孵化和预览的多项创新特性,将它们推向了成熟阶段,同时也引入了一些全新的功能。这个版本被誉为 Java 平台的又一次重大飞跃,尤其是在并发编程、模式匹配、API 规范化等方面带来了革命性的变化。

本文将对 JDK 21 的核心特性进行深度解析,帮助读者理解这些新功能的重要意义、工作原理以及它们将如何影响未来的 Java 开发。

1. 颠覆性的并发模型:虚拟线程 (Virtual Threads, JEP 444)

如果说 JDK 21 中有一个特性最引人注目,那非虚拟线程莫属。这是 Project Loom 项目经过多个预览和孵化阶段后,终于在 JDK 21 中正式落地(Final)。虚拟线程的目标是极大地简化高吞吐量的并发应用开发,特别是在处理大量 I/O 密集型任务时。

背景与问题:

传统的 Java 线程(也称为平台线程)是操作系统级别的线程的一对一映射。创建和管理平台线程的开销很高:

  • 资源消耗大: 每个平台线程都需要一定的内存栈空间(通常是几兆字节),创建大量线程会迅速耗尽系统资源。
  • 创建销毁慢: 线程的创建和销毁涉及操作系统调用,速度相对较慢。
  • 上下文切换开销: 操作系统在不同线程之间切换(上下文切换)会消耗 CPU 资源。

这些限制导致传统的“一请求一线程”模型在高并发场景下难以扩展,开发者不得不采用复杂的异步编程模型(如 CompletableFuture、反应式编程框架)来提高吞吐量。虽然这些模型可以解决问题,但它们往往增加了代码的复杂性,降低了可读性和可维护性,并且调试困难。

虚拟线程的解决方案:

虚拟线程是一种轻量级的、由 JVM 管理的线程。它们与操作系统线程一对一绑定。相反,许多虚拟线程可以映射到少量的平台线程上(这些平台线程在虚拟线程的语境中被称为“载体线程” Carrier Threads)。当一个虚拟线程执行 I/O 阻塞操作(如网络请求、文件读写)时,JVM 可以“卸载”这个虚拟线程,释放其占用的载体线程去执行其他虚拟线程的任务,直到阻塞操作完成再“重新加载”回来。这个过程对开发者是透明的。

核心优势:

  • 极高的可扩展性: 可以轻松创建和运行数百万甚至更多的虚拟线程,远远超过平台线程的数量限制。
  • 简化并发编程: 开发者可以继续使用传统的、直观的阻塞式代码风格来编写高并发应用,而无需陷入复杂的异步回调或响应式流。代码更易读、易写、易调试。
  • 更低的资源消耗: 虚拟线程的内存开销极小(通常是几千字节),远低于平台线程。
  • 框架兼容性: 许多现有的 Java 框架(如 Spring、Jakarta EE 等)可以相对容易地集成虚拟线程,让现有应用也能受益。

如何使用虚拟线程:

使用虚拟线程非常简单,主要通过 java.lang.Thread 类的新 API 实现:

“`java
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class VirtualThreadsExample {

public static void main(String[] args) throws InterruptedException {
    // 方式一:使用 Thread.ofVirtual() 直接创建并启动
    Thread virtualThread1 = Thread.ofVirtual().start(() -> {
        System.out.println("Virtual Thread 1 started: " + Thread.currentThread());
        try {
            Thread.sleep(1000); // 模拟阻塞 I/O 操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Virtual Thread 1 finished: " + Thread.currentThread());
    });

    // 方式二:使用 Executors.newVirtualThreadPerTaskExecutor() 创建执行器
    // 推荐方式,特别是在需要管理大量短期任务时
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.submit(() -> {
            System.out.println("Virtual Thread 2 started: " + Thread.currentThread());
            try {
                Thread.sleep(1000); // 模拟阻塞 I/O 操作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Virtual Thread 2 finished: " + Thread.currentThread());
        });

        executor.submit(() -> {
             System.out.println("Virtual Thread 3 started: " + Thread.currentThread());
            try {
                Thread.sleep(1000); // 模拟阻塞 I/O 操作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Virtual Thread 3 finished: " + Thread.currentThread());
        });

        // 注意:executor.submit() 是异步的,主线程需要等待
        // 在实际应用中,可能是Web服务器接受请求并提交任务,无需主线程等待
    } // try-with-resources 会等待所有提交的任务完成

    // 等待直接启动的虚拟线程完成
    virtualThread1.join();

    System.out.println("All virtual threads finished.");
}

}
“`

上面的例子展示了两种创建虚拟线程的方式。newVirtualThreadPerTaskExecutor() 是处理大量短期并发任务的推荐方式,它为每个提交的任务创建一个新的虚拟线程。Thread.ofVirtual().start() 适合创建生命周期较长的后台任务虚拟线程。

潜在的“钉扎”问题:

虽然虚拟线程通常不会阻塞其载体线程,但在某些特定情况下可能会发生“钉扎”(Pinning),即虚拟线程长时间占用载体线程不释放,这会降低虚拟线程的并发度。常见导致钉扎的情况包括:

  • synchronized 同步块或方法内部执行阻塞 I/O 操作。
  • 执行大量 CPU 密集型计算而没有让出 CPU。

在开发过程中应尽量避免在 synchronized 块内进行阻塞 I/O。对于 CPU 密集型任务,虚拟线程并不比平台线程有优势,应考虑使用传统的 ExecutorService 配合固定数量的平台线程来处理。未来版本的 Java 可能会进一步优化钉扎问题。

总结: 虚拟线程是 JDK 21 最重要的特性,它极大地提升了 Java 平台处理高并发 I/O 密集型任务的能力,并允许开发者回归到更直观的编程风格。

2. 增强的模式匹配:Pattern Matching for switch (JEP 441 – Final) 和 Record Patterns (JEP 440 – Final)

JDK 在最近几个版本中持续改进模式匹配功能,以简化数据处理和结构化数据的解构。JDK 21 将 switch 的模式匹配和记录模式都提升到了最终状态。

2.1 Pattern Matching for switch (JEP 441 – Final):

在 JDK 17 和 JDK 18 中作为预览特性引入后,switch 的模式匹配在 JDK 21 中正式可用。它扩展了 switch 语句和表达式的功能,使其能够根据对象的类型、属性甚至 null 值来进行更复杂的匹配。

背景与问题:

传统的 switch 语句/表达式只能匹配有限的类型:原始类型、枚举类型、String 以及其包装类。对于基于类型的分支逻辑,开发者通常需要使用冗长的 if-instanceof-cast 链:

java
Object obj = ...;
if (obj instanceof Integer) {
Integer i = (Integer) obj;
System.out.println("Integer: " + i * 2);
} else if (obj instanceof String) {
String s = (String) obj;
System.out.println("String length: " + s.length());
} else if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
System.out.println("List size: " + list.size());
} else {
System.out.println("Unknown type");
}

这种代码冗长、重复且容易出错。

switch 模式匹配的解决方案:

允许在 switchcase 标签中使用模式。这使得根据类型进行匹配和提取值变得简洁而强大。

java
Object obj = ...;
String description = switch (obj) {
case Integer i -> "Integer: " + i * 2;
case String s -> "String length: " + s.length();
case List<?> list -> "List size: " + list.size();
case null -> "It's null!"; // 可以直接处理 null
default -> "Unknown type";
};
System.out.println(description);

核心优势:

  • 更简洁: 消除了冗余的类型检查和强制转换。
  • 更安全: 编译器可以对 switch 表达式的模式进行穷尽性检查(Exhaustiveness Checking),确保所有可能的输入都被处理,避免运行时错误(对于语句是非强制的)。
  • 更易读: 代码意图更清晰,结构更紧凑。
  • 处理 null 可以直接在 case 中处理 null,避免额外的 if (obj == null) 检查。
  • 卫语句 (Guarded Patterns): 可以在模式后添加 when 子句,进一步细化匹配条件。
    java
    Object obj = ...;
    String result = switch (obj) {
    case String s when s.length() > 5 -> "Long string: " + s;
    case String s -> "Short string: " + s;
    default -> "Not a string";
    };

2.2 Record Patterns (JEP 440 – Final):

记录模式与 switch 的模式匹配(以及 instanceof)结合使用,极大地简化了对 Record 类型实例进行解构和访问其组件的操作。它也是经过多次预览后在 JDK 21 中最终确定。

背景与问题:

Record 类型(在 JDK 14 中预览,JDK 16 最终确定)是一种紧凑的类声明方式,用于表示不可变的数据载体。要访问 Record 的组件,需要调用对应的访问器方法:

“`java
record Point(int x, int y) {}

Object obj = new Point(1, 2);

if (obj instanceof Point) {
Point p = (Point) obj;
System.out.println(“Point coordinates: (” + p.x() + “, ” + p.y() + “)”);
}
或者使用 `switch` 模式匹配:java
Object obj = new Point(1, 2);
switch (obj) {
case Point p -> System.out.println(“Point coordinates: (” + p.x() + “, ” + p.y() + “)”);
default -> System.out.println(“Not a Point”);
}
``
虽然比
if-instanceof-cast简洁,但仍然需要显式地引入一个变量p` 并调用其方法。

记录模式的解决方案:

允许在模式中直接解构 Record,提取其组件作为新的变量:

“`java
record Point(int x, int y) {}
record Rectangle(Point upperLeft, Point lowerRight) {}

Object obj = new Point(1, 2);

// 在 instanceof 中使用 Record Patterns
if (obj instanceof Point(int x, int y)) {
System.out.println(“Point coordinates: (” + x + “, ” + y + “)”); // 直接使用 x, y
}

Object rectObj = new Rectangle(new Point(0, 0), new Point(10, 10));

// 在 switch 中使用 Record Patterns,支持嵌套
String description = switch (rectObj) {
case Rectangle(Point ul, Point lr) -> // 解构 Rectangle 得到 ul 和 lr
“Rectangle from (” + ul.x() + “,” + ul.y() + “) to (” + lr.x() + “,” + lr.y() + “)”;
case Rectangle(Point(int ulX, int ulY), Point(int lrX, int lrY)) -> // 嵌套解构
“Rectangle from (” + ulX + “,” + ulY + “) to (” + lrX + “,” + lrY + “)”;
default -> “Not a Rectangle”;
};
System.out.println(description);
“`

核心优势:

  • 更简洁: 消除了显式调用 Record 访问器方法的需要。
  • 更清晰: 代码直接反映了对数据结构的解构操作。
  • 支持嵌套: 可以方便地解构包含 Record 的 Record,处理复杂的数据结构。

总结: switch 的模式匹配和记录模式是 Java 在处理结构化数据和基于类型的控制流方面迈出的重要一步,它们共同提高了代码的可读性、安全性和简洁性。

3. 标准化有序集合:Sequenced Collections (JEP 431 – Final)

尽管 Java Collections Framework 已经非常成熟,但长期以来,对于具有确定顺序的集合类型(如 ListDequeLinkedHashSet 等),缺乏一个统一的接口来表达它们共同的“序列化”特性和操作。JDK 21 引入了一组新的接口来填补这一空白。

背景与问题:

在 Java 集合体系中,有些集合(如 ArrayListLinkedListLinkedHashSetDeque 的实现)维护元素的插入顺序或某种定义的顺序,而有些集合(如 HashSetHashMap)则没有确定的顺序。对于有序集合,开发者常常需要执行一些通用的操作,比如获取第一个/最后一个元素,或者获取反序视图。然而,这些操作在不同的有序集合类型上的 API 并不统一:

  • Listget(0)get(size() - 1),但没有标准的 getFirst/getLast 方法。
  • DequegetFirst/getLastaddFirst/addLast 等方法,但这些方法只存在于 Deque 接口,不适用于 ListLinkedHashSet
  • 获取反序视图的操作在 List 中有 Collections.reverseOrder() 或手动实现,在 DequeLinkedHashSet 中则没有统一的 API。

这种不一致性增加了学习成本和代码编写的复杂性。

Sequenced Collections 的解决方案:

引入三个新的接口:

  • SequencedCollection<E>: 继承 Collection<E>,提供访问和添加/移除第一个和最后一个元素的方法,以及获取反序视图的方法。
  • SequencedSet<E>: 继承 Set<E>SequencedCollection<E>,表示一个具有定义顺序的集合,不允许重复元素。
  • SequencedMap<K, V>: 继承 Map<K, V>,提供访问和添加/移除第一个/最后一个映射关系的方法,以及获取反序视图的方法。

这些接口定义了如下关键方法(及其对应的 removeadd 方法):

  • E getFirst()
  • E getLast()
  • SequencedCollection<E> reversed()
  • SequencedSet<E> reversed()
  • SequencedMap<K, V> reversed()

现有的有序集合类(如 ArrayList, LinkedList, ArrayDeque, LinkedHashSet, LinkedHashMap, TreeMap 等)都被修改实现了这些新的接口。

核心优势:

  • API 统一性: 为所有具有确定顺序的集合类型提供了标准化的操作接口。
  • 代码清晰性: 使用 getFirst()getLast()reversed() 等方法更能清晰地表达代码意图。
  • 互操作性: 不同类型的有序集合之间可以更容易地进行通用操作。
  • 新的功能: 例如,现在可以直接对 LinkedHashSet 调用 reversed() 获取反序视图。

示例:

“`java
import java.util.*;

public class SequencedCollectionsExample {

public static void main(String[] args) {
    // List 实现了 SequencedCollection
    List<String> list = new LinkedList<>(List.of("apple", "banana", "cherry"));
    System.out.println("Original List: " + list);
    System.out.println("First element: " + list.getFirst());
    System.out.println("Last element: " + list.getLast());
    System.out.println("Reversed List view: " + list.reversed());
    list.addFirst("orange");
    list.addLast("date");
    System.out.println("After addFirst/addLast: " + list);

    System.out.println("---");

    // LinkedHashSet 实现了 SequencedSet
    Set<String> set = new LinkedHashSet<>(Set.of("red", "green", "blue"));
    System.out.println("Original Set: " + set);
    System.out.println("First element: " + set.getFirst());
    System.out.println("Last element: " + set.getLast());
    System.out.println("Reversed Set view: " + set.reversed()); // 新增功能
    set.addFirst("yellow"); // 新增功能
    set.addLast("purple"); // 新增功能
    System.out.println("After addFirst/addLast: " + set);

    System.out.println("---");

    // LinkedHashMap 实现了 SequencedMap
    Map<Integer, String> map = new LinkedHashMap<>();
    map.put(1, "one");
    map.put(2, "two");
    map.put(3, "three");
    System.out.println("Original Map: " + map);
    System.out.println("First entry: " + map.firstEntry());
    System.out.println("Last entry: " + map.lastEntry());
    System.out.println("Reversed Map view: " + map.reversed()); // 新增功能
    map.putFirst(0, "zero"); // 新增功能
    map.putLast(4, "four"); // 新增功能
    System.out.println("After putFirst/putLast: " + map);
}

}
“`

总结: Sequenced Collections 为 Java 集合框架带来了急需的标准化和便利性,使得处理有序集合变得更加一致和直观。

4. 简洁的语法糖:Unnamed Patterns and Variables (JEP 443 – Final)

这是一个较小的但非常实用的语法改进,它允许使用下划线 _ 来表示一个不需要命名的模式变量或局部变量。

背景与问题:

在某些情况下,我们需要声明一个变量(例如,在 catch 块中捕获异常,或在模式匹配中匹配某个组件)但实际上并不打算使用这个变量。这会导致编译器警告(“variable is never used”)或者为了消除警告而不得不给变量取一个无意义的名字。例如:

“`java
try {
// … some code that might throw IOException
} catch (IOException e) { // ‘e’ might not be used
System.out.println(“An I/O error occurred.”);
}

record Point(int x, int y) {}

Point p = new Point(1, 2);
if (p instanceof Point(int ignoreX, int y)) { // ignoreX is not used
System.out.println(“Y coordinate is: ” + y);
}
“`

Unnamed Patterns and Variables 的解决方案:

使用下划线 _ 作为变量名或模式变量名来明确表示该变量是故意的、不被使用的。

“`java
try {
// … some code that might throw IOException
} catch (IOException _) { // Using _ for unused exception variable
System.out.println(“An I/O error occurred.”);
}

record Point(int x, int y) {}

Point p = new Point(1, 2);
// Using _ in record pattern to ignore the first component
if (p instanceof Point(_, int y)) {
System.out.println(“Y coordinate is: ” + y);
}

// 在 lambda 表达式中忽略参数
// Consumer printer = _ -> System.out.println(“Received something”);
// printer.accept(“hello”); // Prints “Received something”
“`

核心优势:

  • 提高代码清晰度: 明确表达了某个变量是被故意忽略的,而不是遗漏使用。
  • 消除警告: 避免了编译器关于未使用变量的警告。
  • 减少样板代码: 无需为未使用的变量想一个名字。

限制:

_ 不能用于所有上下文,例如不能用于顶级变量声明、字段名、方法参数(除非是 lambda 参数)等。它主要用于 catch 块、增强 for 循环(未来可能支持)以及模式匹配中的变量。

总结: Unnamed Patterns and Variables 是一个小的但有用的语法糖,提高了代码的整洁性和可读性。

5. 简化入门和脚本编写:Unnamed Classes and Instance Main Methods (Preview – JEP 463)

这是一个重要的预览特性,旨在降低 Java 的入门门槛,并使其更适合编写小型程序或脚本。

背景与问题:

编写一个最简单的 Java 程序需要大量的样板代码:一个公共类、一个公共静态 main 方法,以及 String[] args 参数。这对于初学者来说可能感到困惑和繁琐。对于只包含几行代码的小工具或脚本,这些仪式性的结构显得不必要。

java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

Unnamed Classes and Instance Main Methods 的解决方案:

允许在源文件中省略类的显式声明。编译器会隐含地创建一个“ unnamed class ”来包含源文件中的顶级声明(方法、字段等)。此外,允许一个非静态的 main 方法作为程序入口点,并且无需接收 String[] args 参数。

一个简单的“Hello, World!”程序可以简化为:

java
// HelloWorld.java
System.out.println("Hello, World!");

更复杂的例子,包含方法声明:

“`java
// Greeting.java
void greet(String name) {
System.out.println(“Hello, ” + name + “!”);
}

void main() { // Instance main method, implicitly in the unnamed class
greet(“Alice”);
greet(“Bob”);
}
“`

核心优势:

  • 降低入门门槛: 初学者无需理解类和静态方法的概念,即可开始编写和运行 Java 代码。
  • 简化脚本编写: Java 可以更容易地用于编写简短的实用脚本。
  • 减少样板代码: 对于小型程序,代码更加紧凑。

状态与影响:

这个特性在 JDK 21 中是预览状态(Preview),意味着它可能在未来的版本中发生变化或被移除。它主要面向教学和轻量级脚本场景,不建议在大型或核心的生产代码中使用。然而,它的出现表明了 Java 在不断努力提高开发者的效率和平台的易用性。

总结: Unnamed Classes and Instance Main Methods 是一个实验性的尝试,旨在使 Java 对于新手和脚本编写者更加友好。

6. 新的并发上下文传播机制:Scoped Values (Preview – JEP 446)

这也是 Project Loom 的一部分,旨在提供一种比 ThreadLocal 更安全、更易于管理的方式来在多线程或虚拟线程执行期间共享不可变数据。

背景与问题:

ThreadLocal 允许每个线程拥有其独立的变量副本。这在某些场景下很有用(例如,存储当前请求的用户信息、事务上下文等)。然而,ThreadLocal 有一些缺点,尤其是在与虚拟线程结合使用时:

  • 可变性: ThreadLocal 通常存储可变对象,这可能导致难以追踪的副作用。
  • 继承问题: InheritableThreadLocal 可以将值传递给子线程,但这在虚拟线程或大型线程池中可能效率低下或行为不明确。
  • 生命周期与资源管理: ThreadLocal 的生命周期与线程绑定,如果使用不当(例如,没有在线程结束或任务完成后调用 remove()),可能导致内存泄漏。
  • 性能开销: 在虚拟线程频繁切换的场景下,ThreadLocal 的查找和管理开销可能比较高。

Scoped Values 的解决方案:

引入 ScopedValue 类,它提供了一种将不可变值绑定到特定代码执行范围内的机制。这个值可以在该范围内的任何地方被访问,包括由该范围代码启动的虚拟线程或子任务。

  • 值是不可变的。
  • 绑定是有时限的,只在特定的 run()call() 方法执行期间有效。
  • 绑定在虚拟线程之间是高效共享的,而不是复制。
  • 默认情况下,绑定是继承的,无需像 InheritableThreadLocal 那样特殊的处理。

如何使用 (Preview API):

“`java
import java.util.concurrent.Executors;
import jdk.incubator.concurrent.ScopedValue; // 注意这是预览 API

public class ScopedValueExample {

// 定义一个 ScopedValue,泛型指定存储的值类型
static final ScopedValue<String> USER_ID = ScopedValue.newInstance();

public static void main(String[] args) throws Exception {
    // 在顶级作用域没有绑定值
    printUserId(); // 会打印 "User ID: not bound"

    // 使用 ScopedValue.where(value).run(() -> ...) 绑定值并执行代码块
    ScopedValue.where(USER_ID, "user123").run(() -> {
        System.out.println("Inside bounded scope 1:");
        printUserId(); // 打印 "User ID: user123"

        // 在此作用域内启动虚拟线程,值会自动继承
        Thread.ofVirtual().start(() -> {
            System.out.println("  Inside virtual thread in scope 1:");
            printUserId(); // 打印 "User ID: user123"
        }).join();

        // 嵌套绑定
        ScopedValue.where(USER_ID, "user456").run(() -> {
            System.out.println("  Inside nested bounded scope 2:");
            printUserId(); // 打印 "User ID: user456"
        });

        printUserId(); // 仍然打印 "User ID: user123" (回到外层作用域的值)
    });

    // 回到顶级作用域,值再次不可用
    printUserId(); // 打印 "User ID: not bound"
}

static void printUserId() {
    // 使用 ScopedValue.get() 获取当前绑定值
    String userId = USER_ID.orElse("not bound"); // 如果没有绑定则提供默认值
    System.out.println("User ID: " + userId);
}

}
“`

状态与影响:

这是一个预览特性。它为解决并发环境下的上下文传递问题提供了一个有前景的替代方案,特别适用于虚拟线程。它有望成为现代 Java 并发编程中重要的构建块,尤其是在 Web 框架和服务端应用中传递请求上下文信息。

总结: Scoped Values 是 JDK 21 中引入的另一个与并发相关的重要预览特性,提供了比 ThreadLocal 更安全、更高效的上下文传递机制,与虚拟线程相得益彰。

7. 持续演进的底层 API

JDK 21 也包含了几个旨在提升 Java 平台与底层系统交互能力和性能的特性:

7.1 Foreign Function & Memory API (Third Preview – JEP 442):

这个 API (FFM API) 旨在取代 Java Native Interface (JNI),提供一种更安全、更高效、更易用的方式让 Java 程序能够调用外部原生库函数(Foreign Functions)并安全地访问外部内存(Foreign Memory)。经过多个孵化和预览阶段,FFM API 在 JDK 21 中达到了第三次预览。

优势:

  • 安全性: FFM API 提供了内存访问的边界检查,减少了 JNI 中常见的内存错误(如段错误)。
  • 易用性: API 设计更符合 Java 风格,避免了 JNI 中复杂的 C 语言编程和接口。
  • 性能: 目标是达到或超越 JNI 的性能。

状态: 第三次预览。意味着 API 可能仍有变化,但正在逐步稳定。它是 Java 平台与原生世界交互的未来方向。

7.2 Vector API (Sixth Incubator – JEP 448):

Vector API 提供了一种表达矢量计算(Vector Computations)的方式,可以在支持 SIMD(Single Instruction, Multiple Data)指令集的现代 CPU 上实现显著的性能提升。这个 API 已经经历了多个孵化阶段。

优势:

  • 性能: 可以在合适的硬件上将多个操作并行化为一个向量指令,极大地加速数值计算、图像处理、机器学习等任务。
  • 可移植性: 开发者使用 Java API 编写向量计算,JVM 会在运行时将其映射到目标平台的最佳向量指令集。

状态: 第六次孵化。这意味着它仍是一个实验性 API,尚未稳定,主要供早期采用者和专家评估和提供反馈。它代表了 Java 在利用现代硬件并行能力方面的长期努力。

7.3 Generational ZGC (JEP 439 – Final):

Z Garbage Collector (ZGC) 是一款低延迟的垃圾收集器。在 JDK 21 中,ZGC 被增强以支持分代收集(Generational GC)。这意味着 ZGC 现在可以将堆内存划分为新生代和老年代,对存活时间较短的对象更频繁地进行垃圾收集,从而降低了 GC 暂停时间,提高了效率。

优势:

  • 更低的延迟: 分代收集减少了每次 GC 扫描的对象数量,进一步降低了暂停时间。
  • 更高的吞吐量: 减少了不必要的老年代扫描。
  • 更低的内存开销: 对于有大量短生命周期对象的应用,可以减少堆的使用。

状态: Final。这使得 ZGC 成为一个更加强大和适用于更广泛应用场景的低延迟 GC 选项。

8. 其他值得关注的更新

除了上述核心特性,JDK 21 还包含其他一些 JEP 和改进:

  • JEP 451: Prepare to Disallow the Dynamic Loading of Agents: 准备默认禁止在 JVM 启动后动态加载代理(如 javaagent),以提高安全性。这可能会影响一些工具(如监控、剖析工具)的使用方式。
  • JEP 416: Foreign Function and Memory API (Third Preview) – 已在前文提及。
  • JEP 424: Foreign Function and Memory API (Second Preview) – 这是 JEP 442 的前一个版本,在 JDK 19 中。
  • JEP 440: Record Patterns (Final) – 已在前文提及。
  • JEP 441: Pattern Matching for switch (Final) – 已在前文提及。
  • JEP 443: Unnamed Patterns and Variables (Final) – 已在前文提及。
  • JEP 444: Virtual Threads (Final) – 已在前文提及。
  • JEP 446: Scoped Values (Preview) – 已在前文提及。
  • JEP 448: Vector API (Sixth Incubator) – 已在前文提及。
  • JEP 449: Key Agreement for TLS (RFC 8442): 支持 TLS 1.3 中使用的 RFC 8442 定义的 Key Agreement 方法。
  • JEP 452: Key Encapsulation Mechanism API: 提供了一个用于 Key Encapsulation Mechanism (KEM) 的 API,这是一种用于安全传输对称密钥的技术。
  • JEP 453: Structured Concurrency (Third Preview): 与虚拟线程和 Scoped Values 紧密相关的另一个并发预览特性,旨在通过结构化(如使用 try-with-resources)的方式来管理并发任务组的生命周期和错误处理。虽然在 JDK 21 中是第三次预览,但与虚拟线程正式落地相比,受到的关注相对较少,但它与 Loom 的未来发展密切相关。
  • SecurityManager 已被废弃并计划在未来移除: 虽然不是 JDK 21 新增的 JEP,但这是一个重要的长期趋势,需要开发者注意。SecurityManager API 已经被标记为废弃并计划在未来版本中完全移除。

9. 对 Java 生态系统的影响

JDK 21 LTS 的发布将对整个 Java 生态系统产生深远影响:

  • 并发编程范式变革: 虚拟线程将促使框架(如 Spring Boot, Quarkus, Micronaut 等)、库(如 HTTP 客户端、数据库驱动等)以及开发者重新思考和设计高并发应用的架构。传统的反应式编程在某些场景下可能会被基于虚拟线程的阻塞式代码替代。
  • 库和框架更新: 为了充分利用虚拟线程和 Pattern Matching 等新特性,各种 Java 库和框架都需要进行更新和适配。许多流行的框架已经在积极集成虚拟线程支持。
  • 开发者技能要求: 开发者需要学习和掌握虚拟线程、模式匹配等新特性,理解它们的工作原理和最佳实践。
  • 性能提升: Generational ZGC 的普及、FFM API 的成熟以及 Vector API 的潜在应用将为 Java 应用带来显著的性能提升。

10. 结论

JDK 21 LTS 是 Java 平台发展史上的一个重要里程碑。虚拟线程的正式落地标志着 Java 在高并发领域迈出了革命性的一步,极大地简化了 I/O 密集型应用的开发和扩展。Pattern Matching for switch 和 Record Patterns 显著提升了代码的简洁性、可读性和安全性。Sequenced Collections 填补了集合框架长期存在的 API 空缺。同时,Scoped Values、Unnamed Classes/Main Methods 等预览特性展现了 Java 在易用性和现代编程范式探索上的持续努力。底层 API(FFM API, Vector API, Generational ZGC)的进步则不断提升了 Java 平台的性能和与外部世界的交互能力。

对于企业和开发者而言,升级到 JDK 21 LTS 是一个非常值得考虑的选择。它不仅提供了长期稳定的支持,更重要的是带来了能够显著提升开发效率和应用性能的核心新特性。虽然某些新特性(如虚拟线程)的全面应用需要时间和生态系统的成熟,但它们无疑为 Java 的未来发展奠定了坚实的基础。

JDK 21 不仅仅是一个版本更新,它是 Java 平台对当前技术挑战(高并发、复杂数据处理)的有力回应,并为开发者提供了构建更高效、更易维护、更具扩展性的应用的强大工具集。拥抱 JDK 21,意味着拥抱 Java 的未来。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部