Java 21 到 Java 26 新特性详解:虚拟线程、模式匹配、结构化并发、HTTP/3 与 AOT

Java 21 到 Java 26 新特性详解:虚拟线程、模式匹配、结构化并发、HTTP/3 与 AOT

从 Java 21 到 Java 26,Java 平台经历的并不是一次简单的语法升级,而是一轮覆盖并发模型、语言表达能力、启动性能、网络协议、垃圾收集器、安全边界与开发体验的系统演进。

这几个版本中,最值得关注的五条主线是:

  • Java 21 正式交付虚拟线程,让"一个请求一个线程"的同步编程模型重新具备高并发扩展能力。
  • Java 21 正式交付记录模式与 switch 模式匹配,Java 的数据检查与解构能力趋于统一。
  • 结构化并发从 Java 21 一直预览到 Java 26,API 在 Java 25 和 Java 26 中发生了重要变化。
  • Java 26 的标准 HTTP Client 正式支持 HTTP/3,但默认协议仍然是 HTTP/2。
  • Project Leyden 从 Java 24 开始逐步交付 AOT 缓存能力,持续改善 JVM 的启动和预热速度。

本文基于截至 2026 年 8 月 27 日已经正式发布的 JDK 21---26 编写。文中会明确标注"正式""预览""孵化器"和"实验性"状态,避免把尚未定版的 API 当成稳定能力。


1. Java 21 到 Java 26 版本全景

1.1 发布时间与版本定位

Java 版本 GA 日期 Class 文件主版本 JEP 数量 版本定位
Java 21 2023-09-19 65 15 多数主流厂商提供 LTS,虚拟线程与模式匹配正式落地
Java 22 2024-03-19 66 12 FFM API、未命名变量正式,多个 API 继续预览
Java 23 2024-09-17 67 12 原始类型模式匹配首预览,ZGC 默认使用分代模式
Java 24 2025-03-18 68 24 AOT、Stream Gatherers、Class-File API 与虚拟线程改进集中交付
Java 25 2025-09-16 69 18 多数主流厂商提供 LTS,Scoped Values 与多项语言特性正式定版
Java 26 2026-03-17 70 10 HTTP/3 正式落地,AOT 支持任意 GC,结构化并发第六次预览

需要注意,LTS 是 JDK 发行商的长期支持策略,不是 Java 语言规范中的关键字。Oracle、Eclipse Temurin、Amazon Corretto、Azul Zulu 等发行版的支持周期和商业条款可能不同,生产选型时应以实际采用的发行版为准。

1.2 六个版本最值得关注的能力

能力 首次出现在本文范围 当前状态(Java 26) 主要价值
虚拟线程 Java 21 正式 正式 用同步阻塞代码支撑大量 I/O 并发任务
记录模式 Java 21 正式 正式 直接解构 record,减少类型转换和样板代码
switch 模式匹配 Java 21 正式 正式 类型匹配、守卫条件、穷举检查统一进入 switch
原始类型模式匹配 Java 23 预览 Java 26 第四次预览 instanceof、模式和 switch 支持所有基本类型
结构化并发 Java 21 预览 Java 26 第六次预览 把一组并发子任务作为一个生命周期单元管理
Scoped Values Java 21 预览 Java 25 起正式 在线程调用链中安全、高效地传递只读上下文
FFM API Java 21 第三次预览 Java 22 起正式 用标准 API 调用本地函数和访问堆外内存
Stream Gatherers Java 22 预览 Java 24 起正式 为 Stream 增加窗口、折叠等自定义中间操作
Class-File API Java 22 预览 Java 24 起正式 标准化 class 文件解析、生成和转换
AOT 缓存 Java 24 Java 26 持续增强 改善启动时间和到达峰值性能前的预热时间
HTTP/3 Java 26 正式 标准 HTTP Client 可通过 QUIC 访问 HTTP/3 服务

2. 先分清正式、预览、孵化器和实验性特性

阅读新版 Java 特性时,最容易踩的坑不是语法,而是误判特性的稳定状态。

状态 含义 是否需要额外参数 生产建议
正式特性 已进入 Java SE 或 JDK 正式能力,兼容性承诺最强 不需要 可以正常评估并投入生产
Preview 设计已较完整,但仍可能变更或撤回 编译、运行都要 --enable-preview 适合试点,不宜暴露为长期公共 API
Incubator 仍处于孵化阶段,通常位于 jdk.incubator.* 模块 通常需要 --add-modules 适合实验、压测和反馈
Experimental JVM 或运行时实验能力 通常需要专用 JVM 参数 必须经过专项验证,不应默认开启

以 Java 26 的预览代码为例:

bash 复制代码
javac --enable-preview --release 26 Demo.java
java --enable-preview Demo

预览特性还有两个重要限制:

  1. 预览 class 文件与具体 Java 版本绑定。例如使用 Java 25 预览特性编译的代码,不能把"预览兼容"理所当然地延续到 Java 26。
  2. 预览特性可能改变甚至撤回。Java 21 和 Java 22 预览过的 String Templates 后来没有进入 Java 23,相关第三次预览 JEP 465 已被撤回。

因此,业务代码使用预览 API 时,最好集中封装在少量模块中,不要扩散到公共 SDK、领域对象和跨服务接口。


3. Java 21:虚拟线程与模式匹配正式落地

Java 21 是这一轮演进的起点,也是很多企业从 Java 8、11 或 17 升级时选择的长期运行基线。

3.1 Java 21 的 15 个 JEP

分类 JEP 与特性 状态
并发 JEP 444:Virtual Threads 正式
并发 JEP 446:Scoped Values 预览
并发 JEP 453:Structured Concurrency 预览
语言 JEP 440:Record Patterns 正式
语言 JEP 441:Pattern Matching for switch 正式
语言 JEP 443:Unnamed Patterns and Variables 预览
语言 JEP 445:Unnamed Classes and Instance Main Methods 预览
语言/API JEP 430:String Templates 预览,后续未定版
集合 JEP 431:Sequenced Collections 正式
内存与本地互操作 JEP 442:Foreign Function & Memory API 第三次预览
GC JEP 439:Generational ZGC 正式
向量计算 JEP 448:Vector API 第六次孵化
安全 JEP 452:Key Encapsulation Mechanism API 正式
工具与兼容性 JEP 451:准备禁止动态加载 Agent 正式行为变更
平台清理 JEP 449:弃用 Windows 32 位 x86 端口并准备移除 弃用

3.2 Sequenced Collections:统一集合的首尾操作

Java 21 引入 SequencedCollectionSequencedSetSequencedMap,为具有确定遍历顺序的集合补齐统一的首尾操作和反向视图。

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class SequencedCollectionDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("B", "C"));

        names.addFirst("A");
        names.addLast("D");

        System.out.println(names.getFirst()); // A
        System.out.println(names.getLast());  // D
        System.out.println(names.reversed()); // [D, C, B, A]
    }
}

reversed() 返回的是反向视图,不是一次无条件的完整复制。修改原集合或可修改视图时,需要理解二者之间的关联。

3.3 Generational ZGC

JEP 439 为 ZGC 引入分代模式。它利用"大多数对象朝生夕灭"的代际假设,把年轻对象与长期存活对象区别处理,在保持低停顿目标的同时减少不必要的扫描工作。

Java 21 中可以通过下面的参数启用分代 ZGC:

bash 复制代码
java -XX:+UseZGC -XX:+ZGenerational -jar app.jar

到了 Java 23,ZGC 默认使用分代模式;到了 Java 24,非分代模式被移除。因此升级时不能只复制旧 GC 参数,还要重新核对参数是否仍然有效。


4. 虚拟线程详解:Java 21 并发模型的核心变化

4.1 平台线程为什么昂贵

传统平台线程通常与操作系统线程一一对应。操作系统线程需要栈空间、内核调度和上下文切换,数量过多时会带来明显成本。

因此,过去的 Java 服务常使用固定线程池:

java 复制代码
var executor = java.util.concurrent.Executors.newFixedThreadPool(200);

线程池解决的是"平台线程稀缺"问题,但也带来新的工程问题:

  • 线程数过少时,请求排队,吞吐受限。
  • 线程数过多时,内存和调度成本上升。
  • 异步链路、回调和 CompletableFuture 组合会增加异常处理与调试难度。
  • 线程池大小经常被错误地当作下游服务的并发限流器。

4.2 虚拟线程是什么

虚拟线程仍然是 java.lang.Thread,但由 JVM 调度,不会在整个生命周期中独占某个操作系统线程。

虚拟线程执行 Java 代码时,会挂载到一个平台线程上,这个平台线程称为 carrier thread。遇到 JDK 能识别的阻塞 I/O 时,虚拟线程可以卸载,carrier 随即去执行其他虚拟线程。I/O 就绪后,原虚拟线程再挂载并继续执行。

可以把它理解为:

text 复制代码
大量业务虚拟线程
        ↓ JVM 调度与挂载
少量 carrier 平台线程
        ↓ OS 调度
CPU 核心

虚拟线程的核心价值是提高大量阻塞任务下的吞吐量,而不是让单个任务执行得更快。

4.3 创建虚拟线程

直接创建一个虚拟线程:

java 复制代码
public class VirtualThreadStartDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = Thread.ofVirtual()
                .name("order-query")
                .start(() -> System.out.println(Thread.currentThread()));

        thread.join();
    }
}

为每个任务创建一个虚拟线程:

java 复制代码
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

public class VirtualThreadExecutorDemo {
    public static void main(String[] args) throws Exception {
        List<Callable<String>> tasks = new ArrayList<>();

        for (int i = 0; i < 10_000; i++) {
            int taskId = i;
            tasks.add(() -> {
                Thread.sleep(Duration.ofMillis(100));
                return "task-" + taskId + " -> " + Thread.currentThread();
            });
        }

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var futures = executor.invokeAll(tasks);
            System.out.println(futures.getFirst().get());
            System.out.println("任务数:" + futures.size());
        }
    }
}

newVirtualThreadPerTaskExecutor() 不是一个固定大小的虚拟线程池。它为每个任务创建新的虚拟线程,这正是推荐的使用方式。

4.4 虚拟线程适合什么场景

适合:

  • 大量并发 HTTP/RPC 请求。
  • JDBC、Redis、消息队列等阻塞式客户端调用。
  • 文件和网络 I/O。
  • 请求生命周期清晰的 thread-per-request 服务。
  • 希望保留同步代码、异常栈和传统调试体验的系统。

不一定适合:

  • 长时间占用 CPU 的计算任务。
  • 已经是完整事件循环模型、且没有阻塞调用的系统。
  • 依赖大量昂贵 ThreadLocal 缓存的旧框架。
  • 下游容量很小,却没有并发隔离和背压的调用链。

虚拟线程不会增加 CPU 核心,也不会让 CPU 密集型计算自动并行得更快。CPU 密集型任务仍应根据核心数控制并行度。

4.5 不要用线程池给虚拟线程限流

如果某个下游最多只能承受 50 个并发请求,应使用 Semaphore 表达资源上限,而不是把虚拟线程塞进固定线程池。

java 复制代码
import java.util.concurrent.Semaphore;

public class LimitedRemoteService {
    private final Semaphore permits = new Semaphore(50);

    public String call() throws InterruptedException {
        permits.acquire();
        try {
            return invokeRemoteService();
        } finally {
            permits.release();
        }
    }

    private String invokeRemoteService() throws InterruptedException {
        Thread.sleep(100);
        return "ok";
    }
}

数据库连接池本身就具有类似信号量的容量约束,通常不需要再在连接池外机械叠加一个相同上限的信号量。

4.6 Pinning:从 Java 21 到 Java 26 的变化

在 Java 21 中,虚拟线程如果在 synchronized 代码块内执行长时间阻塞操作,可能被固定在 carrier 上,导致 carrier 无法去执行其他虚拟线程。

Java 21 的典型规避方式是只针对"频繁且长时间阻塞"的位置改用 ReentrantLock

java 复制代码
lock.lock();
try {
    blockingIo();
} finally {
    lock.unlock();
}

Java 24 的 JEP 491 改进了 HotSpot,使虚拟线程在持有 synchronized 监视器时也能在阻塞点卸载。到了 Java 26,常见剩余 pinning 场景主要是执行本地方法或 Foreign Function 时发生阻塞。

这并不代表 synchronized 从此没有死锁、锁竞争或临界区过大的问题。JEP 491 解决的是虚拟线程与 carrier 的固定问题,不会改变 Java 监视器本身的并发语义。

Java 26 还进一步优化了虚拟线程等待其他线程完成类初始化时的卸载行为,减少 carrier 被占住以及极端情况下调度停滞的风险。

4.7 观测虚拟线程

大量虚拟线程不适合只靠传统的操作系统线程视角观察。可以使用:

bash 复制代码
jcmd <pid> Thread.dump_to_file -format=json threads.json

还应结合 JFR、应用指标、连接池指标、下游限流和请求超时一起判断。看到"虚拟线程数量很多"本身并不等于故障,关键要看它们阻塞在哪里、是否持续堆积,以及下游资源是否已经饱和。


5. 模式匹配详解:从 Java 21 的引用类型走向 Java 26 的基本类型

5.1 Java 21 正式支持记录模式

传统代码需要先判断类型,再强制转换,最后调用访问器:

java 复制代码
if (obj instanceof Point) {
    Point point = (Point) obj;
    int x = point.x();
    int y = point.y();
}

记录模式可以直接完成类型检查和数据解构:

java 复制代码
record Point(int x, int y) {}

static int distanceSquared(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        return x * x + y * y;
    }
    return 0;
}

记录模式还支持嵌套解构:

java 复制代码
record Point(int x, int y) {}
record Line(Point start, Point end) {}

static int horizontalLength(Object obj) {
    if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))
            && y1 == y2) {
        return Math.abs(x2 - x1);
    }
    return 0;
}

5.2 Java 21 正式支持 switch 模式匹配

配合 sealed 类型、record 和 switch,可以构建紧凑且可穷举检查的数据处理代码。

java 复制代码
import java.math.BigDecimal;

sealed interface Payment permits CardPayment, CashPayment, CouponPayment {}

record CardPayment(String cardNo, BigDecimal amount) implements Payment {}
record CashPayment(BigDecimal amount) implements Payment {}
record CouponPayment(String code, BigDecimal amount) implements Payment {}

public class PaymentDemo {
    static String describe(Payment payment) {
        return switch (payment) {
            case CardPayment(String cardNo, BigDecimal amount)
                    when amount.compareTo(new BigDecimal("10000")) > 0 ->
                    "大额银行卡支付,尾号:" + cardNo.substring(cardNo.length() - 4);
            case CardPayment(String cardNo, BigDecimal amount) ->
                    "银行卡支付:" + amount;
            case CashPayment(BigDecimal amount) ->
                    "现金支付:" + amount;
            case CouponPayment(String code, BigDecimal amount) ->
                    "优惠券支付:" + code + ",金额:" + amount;
        };
    }
}

这里包含三种能力:

  • 类型模式:判断对象属于哪个实现类型。
  • 记录模式:匹配成功后直接提取 record 组件。
  • when 守卫:在模式匹配成功后继续判断业务条件。

由于 Payment 是密封层次,编译器知道所有允许的子类型。当新增实现类时,未覆盖所有分支的 switch 会在编译期暴露问题。

5.3 null 处理更加明确

模式 switch 可以显式处理 null

java 复制代码
static String format(Object value) {
    return switch (value) {
        case null -> "<null>";
        case String text when text.isBlank() -> "<blank>";
        case String text -> text;
        case Integer number -> "int:" + number;
        default -> value.toString();
    };
}

如果没有 case null,对 null 执行这类 switch 仍会抛出 NullPointerException。不要把模式匹配误解为自动的空安全。

5.4 Java 23---26:基本类型模式匹配持续预览

这条能力的演进路径是:

版本 JEP 状态
Java 23 JEP 455 第一次预览
Java 24 JEP 488 第二次预览
Java 25 JEP 507 第三次预览
Java 26 JEP 530 第四次预览

Java 26 中,可以用 instanceof 检查一次基本类型转换是否会丢失信息,并在成功时直接绑定变量:

java 复制代码
public class PrimitivePatternDemo {
    static void printAsByte(int value) {
        if (value instanceof byte b) {
            System.out.println("可以安全转换为 byte:" + b);
        } else {
            System.out.println("超出 byte 范围:" + value);
        }
    }

    public static void main(String[] args) {
        printAsByte(100);
        printAsByte(1000);
    }
}

编译和运行:

bash 复制代码
javac --enable-preview --release 26 PrimitivePatternDemo.java
java --enable-preview PrimitivePatternDemo

Java 26 还允许 longfloatdoubleboolean 及其包装类型作为 switch 选择器:

java 复制代码
static String rating(double value) {
    return switch (value) {
        case 0.0d -> "零分";
        case double score when score > 0.0d && score < 3.0d -> "较低";
        case double score when score >= 3.0d && score < 5.0d -> "良好";
        case 5.0d -> "满分";
        default -> "非法评分";
    };
}

这仍是预览语法,不能因为已经连续预览四次就把它当作正式语言特性。


6. 结构化并发详解:让并发任务拥有清晰的生命周期

6.1 传统 Future 组合的问题

一个请求可能需要同时查询用户、订单和账户余额:

text 复制代码
处理请求
├── 查询用户
├── 查询订单
└── 查询余额

传统线程池加 Future 可以并发执行,但开发者必须自己处理:

  • 某个子任务失败后,其他任务是否取消。
  • 父任务超时后,子任务是否仍在后台运行。
  • 中断是否正确传播。
  • 多个异常如何汇总。
  • 线程转储中如何看出父子任务关系。

如果生命周期管理不严谨,很容易出现线程泄漏、无效远程调用和取消延迟。

6.2 结构化并发的核心约束

结构化并发借鉴结构化编程的思想:进入一个作用域,创建子任务,等待子任务结束,然后离开作用域。

text 复制代码
进入作用域
   ├── fork 子任务 A
   ├── fork 子任务 B
   ├── join
   └── 得到结果或异常
离开作用域:所有子任务均已结束

父任务不会在子任务仍然失控运行时悄悄结束。作用域把取消、失败传播、超时和可观测性放进同一个结构中。

6.3 Java 21 到 Java 26 的演进

版本 JEP 状态 关键说明
Java 21 JEP 453 第一次预览 从孵化器进入预览 API
Java 22 JEP 462 第二次预览 延续反馈与验证
Java 23 JEP 480 第三次预览 延续预览
Java 24 JEP 499 第四次预览 延续预览
Java 25 JEP 505 第五次预览 API 重新设计,引入 openJoiner 与配置模型
Java 26 JEP 525 第六次预览 小幅调整命名、超时结果和配置函数类型

网上很多示例仍使用早期版本的 new StructuredTaskScope.ShutdownOnFailure()。这不是 Java 26 推荐的 API 形态,直接复制很可能无法编译。

6.4 Java 26 的基础示例

java 复制代码
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

public class ProfileService {
    record User(long id, String name) {}
    record Order(long id, long userId) {}
    record Profile(User user, List<Order> orders, int balance) {}

    static Profile loadProfile(long userId) throws InterruptedException {
        try (var scope = StructuredTaskScope.open()) {
            Subtask<User> userTask = scope.fork(() -> loadUser(userId));
            Subtask<List<Order>> orderTask = scope.fork(() -> loadOrders(userId));
            Subtask<Integer> balanceTask = scope.fork(() -> loadBalance(userId));

            // 默认策略:全部成功则正常返回,任一失败则抛出 FailedException
            scope.join();

            return new Profile(
                    userTask.get(),
                    orderTask.get(),
                    balanceTask.get()
            );
        }
    }

    static User loadUser(long id) throws InterruptedException {
        Thread.sleep(100);
        return new User(id, "Alice");
    }

    static List<Order> loadOrders(long id) throws InterruptedException {
        Thread.sleep(150);
        return List.of(new Order(1L, id), new Order(2L, id));
    }

    static int loadBalance(long id) throws InterruptedException {
        Thread.sleep(80);
        return 1000;
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(loadProfile(1001L));
    }
}

编译和运行:

bash 复制代码
javac --enable-preview --release 26 ProfileService.java
java --enable-preview ProfileService

默认情况下,StructuredTaskScope 会为每个子任务创建虚拟线程。

6.5 Joiner:显式表达并发结果策略

如果只要第一个成功结果,可以使用 Java 26 的 anySuccessfulOrThrow()

java 复制代码
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Joiner;

static <T> T race(List<Callable<T>> tasks) throws InterruptedException {
    try (var scope = StructuredTaskScope.open(Joiner.<T>anySuccessfulOrThrow())) {
        tasks.forEach(scope::fork);
        return scope.join();
    }
}

第一个任务成功后,作用域会取消仍在运行且不再需要的任务。如果所有任务都失败,join() 抛出 StructuredTaskScope.FailedException

Java 25 的同类方法名是 anySuccessfulResultOrThrow(),Java 26 改为 anySuccessfulOrThrow()。这正说明预览 API 不能依赖跨版本源代码兼容性。

6.6 超时控制

java 复制代码
import java.time.Duration;
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Joiner;

static List<String> loadAll() throws InterruptedException {
    try (var scope = StructuredTaskScope.open(
            Joiner.<String>allSuccessfulOrThrow(),
            config -> config.withTimeout(Duration.ofSeconds(2)))) {

        scope.fork(() -> query("service-a"));
        scope.fork(() -> query("service-b"));
        scope.fork(() -> query("service-c"));

        return scope.join();
    }
}

static String query(String service) throws InterruptedException {
    Thread.sleep(100);
    return service + ":ok";
}

如果超时在 join() 完成前到达,作用域会取消未完成任务,并根据 Joiner 策略抛出 StructuredTaskScope.TimeoutException 或返回自定义超时结果。

6.7 结构化并发与虚拟线程的关系

二者解决不同层次的问题:

  • 虚拟线程解决"一个线程的资源成本过高"问题。
  • 结构化并发解决"多个相关线程如何组成一个可靠任务"问题。
  • Scoped Values 解决"上下文如何沿结构化调用链安全传递"问题。

三者组合后,Java 可以用更直接的同步代码表达高并发请求,同时保留清晰的父子关系、异常传播和线程转储信息。


7. Java 22:FFM API 正式定版,补齐底层互操作能力

7.1 Java 22 的 12 个 JEP

分类 JEP 与特性 状态
GC JEP 423:Region Pinning for G1 正式
语言 JEP 447:Statements before super(...) 预览
本地互操作 JEP 454:Foreign Function & Memory API 正式
语言 JEP 456:Unnamed Variables & Patterns 正式
字节码 JEP 457:Class-File API 预览
启动器 JEP 458:Launch Multi-File Source-Code Programs 正式
语言/API JEP 459:String Templates 第二次预览,后续撤回
向量计算 JEP 460:Vector API 第七次孵化
Stream JEP 461:Stream Gatherers 预览
并发 JEP 462:Structured Concurrency 第二次预览
入门语法 JEP 463:Implicitly Declared Classes and Instance Main Methods 第二次预览
并发上下文 JEP 464:Scoped Values 第二次预览

7.2 Foreign Function & Memory API

FFM API 让 Java 可以通过标准、受控的方式访问堆外内存和本地函数,逐步减少对 JNI 样板代码与 sun.misc.Unsafe 的依赖。

下面示例在受控 Arena 中分配堆外内存:

java 复制代码
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

public class ForeignMemoryDemo {
    public static void main(String[] args) {
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment segment = arena.allocate(ValueLayout.JAVA_INT);
            segment.set(ValueLayout.JAVA_INT, 0, 42);

            int value = segment.get(ValueLayout.JAVA_INT, 0);
            System.out.println(value);
        }
    }
}

Arena 关闭后,其中受管理的内存段失效,从生命周期层面降低悬空指针和忘记释放本地内存的风险。

7.3 未命名变量与模式

当变量必须出现在语法中、但业务上并不关心它的值时,可以使用 _

java 复制代码
try {
    parse();
} catch (NumberFormatException _) {
    System.out.println("格式错误");
}

它能明确告诉读者:这个值有意被忽略,而不是开发者忘记使用变量。

7.4 多文件源码直接启动

Java 22 增强源码启动模式。对于简单演示和脚本式程序,可以直接执行入口源码,并让启动器发现相关源码:

bash 复制代码
java Main.java

它适合教学、原型和小工具,但复杂工程仍应使用 Maven、Gradle 等构建系统管理依赖、测试和制品。


8. Java 23:基本类型模式匹配首预览,ZGC 分代模式成为默认

8.1 Java 23 的 12 个 JEP

分类 JEP 与特性 状态
语言 JEP 455:Primitive Types in Patterns, instanceof, and switch 预览
字节码 JEP 466:Class-File API 第二次预览
文档 JEP 467:Markdown Documentation Comments 正式
向量计算 JEP 469:Vector API 第八次孵化
底层 API JEP 471:弃用 sun.misc.Unsafe 内存访问方法并准备移除 弃用
Stream JEP 473:Stream Gatherers 第二次预览
GC JEP 474:ZGC 默认使用分代模式 正式行为变更
语言 JEP 476:Module Import Declarations 预览
入门语法 JEP 477:Implicitly Declared Classes and Instance Main Methods 第三次预览
并发 JEP 480:Structured Concurrency 第三次预览
并发上下文 JEP 481:Scoped Values 第三次预览
语言 JEP 482:Flexible Constructor Bodies 第二次预览

8.2 Markdown 文档注释

Java 23 支持使用 /// 编写 Markdown 风格的 Javadoc:

java 复制代码
/// 根据用户 ID 查询用户。
///
/// - 找到用户时返回用户对象
/// - 用户不存在时返回空结果
///
/// @param id 用户 ID
/// @return 用户查询结果
User findById(long id) {
    return repository.findById(id);
}

这不会让普通 .md 文件自动变成 Javadoc,而是为 Java 源码中的 API 文档提供另一种书写形式。

8.3 ZGC 的行为变化

Java 23 中,只要使用:

bash 复制代码
java -XX:+UseZGC -jar app.jar

ZGC 就默认采用分代模式。原来的 -XX:+ZGenerational 不再是启用分代模式所必需的开关。升级时应清理已经冗余或即将失效的参数,并重新进行延迟、吞吐和堆占用压测。

8.4 String Templates 没有继续进入 Java 23

Java 21 和 Java 22 的 STR."...\{value}..." 属于预览语法。它没有成为 Java 23 特性,第三次预览提案也已撤回。

这件事很有代表性:预览不是"提前拿到的正式功能",而是一种获取实际反馈的发布机制。生产代码如果使用预览能力,就必须接受升级时改代码甚至移除代码的成本。


9. Java 24:AOT、Stream Gatherers、Class-File API 与虚拟线程集中增强

Java 24 一次交付了 24 个 JEP,是 Java 21---26 期间功能最密集的版本。

9.1 Java 24 的 JEP 全景

分类 主要特性
GC JEP 404 分代 Shenandoah(实验性)、JEP 475 G1 延迟屏障展开、JEP 490 移除非分代 ZGC、JEP 501 弃用 32 位 x86 端口
对象布局 JEP 450 Compact Object Headers(实验性)
本地调用与底层访问 JEP 472 准备限制 JNI、JEP 498 使用 sun.misc.Unsafe 内存访问方法时告警
安全 JEP 478 KDF API(预览)、JEP 496 ML-KEM、JEP 497 ML-DSA、JEP 486 永久禁用 Security Manager
AOT JEP 483 Ahead-of-Time Class Loading & Linking
标准 API JEP 484 Class-File API、JEP 485 Stream Gatherers
虚拟线程 JEP 491 synchronized 场景不再固定虚拟线程
语言预览 JEP 488 基本类型模式第二次预览、JEP 492 灵活构造器第三次预览、JEP 494 模块导入第二次预览、JEP 495 简单源码文件第四次预览
并发预览 JEP 487 Scoped Values 第四次预览、JEP 499 结构化并发第四次预览
向量计算 JEP 489 Vector API 第九次孵化
平台工具 JEP 479 移除 Windows 32 位 x86 端口、JEP 493 无需 JMOD 链接运行时镜像

9.2 Stream Gatherers 正式定版

Gatherer 是 Stream 的自定义中间操作扩展点,可表达固定窗口、滑动窗口、扫描和折叠等操作。

java 复制代码
import java.util.List;
import java.util.stream.Gatherers;
import java.util.stream.Stream;

public class GathererDemo {
    public static void main(String[] args) {
        List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6, 7)
                .gather(Gatherers.windowFixed(3))
                .toList();

        System.out.println(windows);
        // [[1, 2, 3], [4, 5, 6], [7]]
    }
}

过去为了实现这类操作,常常需要先收集成列表、手写循环,或者引入第三方流式库。Gatherer 把它纳入标准 Stream 管线。

9.3 Class-File API 正式定版

java.lang.classfile 提供标准方式解析、生成和转换 .class 文件。字节码工具、代理框架、编译器、覆盖率工具不必只依赖内部 API 或第三方字节码模型。

它并不会自动替代 ASM、Byte Buddy 等成熟生态库,但为 Java 平台自身演进与工具开发提供了稳定、与 class 文件格式同步的标准基础。

9.4 安全边界进一步收紧

Java 24 永久禁用 Security Manager,并继续为限制 JNI 与淘汰 Unsafe 内存访问方法做准备。

升级旧系统时,应重点排查:

  • 是否仍通过 System.setSecurityManager 安装安全管理器。
  • Agent、APM 或框架是否动态加载本地库。
  • 是否直接调用 sun.misc.Unsafe 的内存访问方法。
  • 是否把 --add-opens--add-exports 当作永久解决方案。

这些变化体现了 Java 的"默认完整性"方向:平台会逐步减少任意修改内部状态、类结构和最终字段的通道。


10. Java 25:新的 LTS 基线,Scoped Values 与多项语言能力正式定版

10.1 Java 25 的 18 个 JEP

分类 JEP 与特性 状态
安全 JEP 470:PEM Encodings of Cryptographic Objects 预览
常量 JEP 502:Stable Values 预览
平台 JEP 503:移除 32 位 x86 端口 正式
并发 JEP 505:Structured Concurrency 第五次预览
并发上下文 JEP 506:Scoped Values 正式
模式匹配 JEP 507:基本类型模式匹配 第三次预览
向量计算 JEP 508:Vector API 第十次孵化
JFR JEP 509:CPU-Time Profiling 实验性
安全 JEP 510:Key Derivation Function API 正式
语言 JEP 511:Module Import Declarations 正式
语言 JEP 512:Compact Source Files and Instance Main Methods 正式
语言 JEP 513:Flexible Constructor Bodies 正式
AOT JEP 514:AOT Command-Line Ergonomics 正式
AOT JEP 515:AOT Method Profiling 正式
JFR JEP 518:Cooperative Sampling 正式
对象布局 JEP 519:Compact Object Headers 正式产品特性
JFR JEP 520:Method Timing & Tracing 正式
GC JEP 521:Generational Shenandoah 正式产品特性

10.2 Scoped Values 正式定版

Scoped Value 适合沿调用链传递只读上下文,例如租户、Trace ID、认证主体和请求元数据。

java 复制代码
public class ScopedValueDemo {
    private static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();

    public static void main(String[] args) {
        ScopedValue.where(TRACE_ID, "trace-20260827")
                .run(ScopedValueDemo::handleRequest);
    }

    static void handleRequest() {
        queryOrder();
    }

    static void queryOrder() {
        System.out.println("traceId=" + TRACE_ID.get());
    }
}

ThreadLocal 相比,Scoped Value 的绑定具有明确词法范围,值不可在作用域内被随意修改,也更适合虚拟线程与结构化并发的继承模型。

它不是所有 ThreadLocal 的直接替代品。如果业务确实需要线程内可变状态,就需要重新设计状态所有权,而不是强行把可变对象塞进 Scoped Value。

10.3 模块导入声明

Java 25 可以导入一个模块导出的所有包:

java 复制代码
import module java.base;

public class ModuleImportDemo {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob");
        Map<String, Integer> scores = Map.of("Alice", 100, "Bob", 95);
        System.out.println(names);
        System.out.println(scores);
    }
}

它主要降低教学、探索和模块化库使用时的大量导入噪声,不代表大型工程应该无条件取消精确导入。

10.4 紧凑源码文件与实例 main 方法

下面代码在 Java 25 已是正式语法:

java 复制代码
void main() {
    System.out.println("Hello, Java 25");
}

直接运行:

bash 复制代码
java Hello.java

它降低初学者和小工具的入口门槛,但并没有废除传统的类声明与 public static void main(String[] args)

10.5 灵活的构造器方法体

Java 25 允许在显式调用 super(...)this(...) 之前执行不会访问当前未初始化对象的校验和计算。

java 复制代码
class PositiveValue extends Number {
    private final int value;

    PositiveValue(int value) {
        if (value <= 0) {
            throw new IllegalArgumentException("value 必须大于 0");
        }
        super();
        this.value = value;
    }

    @Override public int intValue() { return value; }
    @Override public long longValue() { return value; }
    @Override public float floatValue() { return value; }
    @Override public double doubleValue() { return value; }
}

它解决了过去为了在 super(...) 前校验参数,不得不引入静态辅助方法或重复计算的问题。


11. Java 26:HTTP/3 正式加入标准客户端,AOT 与运行时继续增强

Java 26 已于 2026 年 3 月 17 日正式发布,GA 构建为 26+35

11.1 Java 26 的 10 个 JEP

JEP 特性 状态
500 Prepare to Make Final Mean Final 正式告警与迁移机制
504 Remove the Applet API 正式移除
516 Ahead-of-Time Object Caching with Any GC 正式
517 HTTP/3 for the HTTP Client API 正式
522 G1 GC: Improve Throughput by Reducing Synchronization 正式
524 PEM Encodings of Cryptographic Objects 第二次预览
525 Structured Concurrency 第六次预览
526 Lazy Constants 第二次预览
529 Vector API 第十一次孵化
530 Primitive Types in Patterns, instanceof, and switch 第四次预览

11.2 Prepare to Make Final Mean Final

Java 26 开始对通过深反射修改 final 字段的行为发出警告,为未来默认限制这种能力做准备。

这不会立刻让所有旧框架失效,但序列化、ORM、依赖注入、Mock 和字节码增强框架都应该检查是否依赖深反射修改最终字段。

迁移的正确方向是:

  • 优先升级相关框架。
  • 使用构造器、工厂方法或受支持的反序列化扩展点。
  • 只在明确模块范围内临时开放能力。
  • 不要把全局放开 final 字段修改当作长期方案。

11.3 Applet API 被移除

Applet API 自 JDK 17 起就已经标记为待移除,Java 26 正式删除。现代浏览器早已不支持 Applet,普通服务端应用通常不会受到影响。

风险主要存在于:

  • 仍保留 Applet 类型引用的历史源码。
  • 反射扫描整个 JDK 类型集合的工具。
  • 打包了年代久远桌面组件的内部系统。

11.4 G1 吞吐优化

JEP 522 通过减少应用线程与 GC 线程之间的同步,提高 G1 场景下的应用吞吐。它属于运行时内部优化,应用通常不需要改代码。

但"升级后自动更快"不能代替压测。GC 表现取决于堆大小、对象分配速率、存活集、CPU、NUMA、容器限制和暂停目标,仍应使用相同业务负载做升级前后对比。

11.5 Lazy Constants 第二次预览

Lazy Constant 表示最多初始化一次、之后不可修改的惰性值。JVM 可以像优化 final 字段一样理解它,同时允许初始化时机延后。

适合的概念场景包括:

  • 首次使用时加载大型模型或字典。
  • 延迟解析配置和元数据。
  • 只在某条业务路径真正需要时初始化昂贵对象。

它仍是预览 API,不应在公共库的稳定 API 中直接暴露。


12. HTTP/3 详解:Java 26 标准客户端如何使用 QUIC

12.1 HTTP/3 与 HTTP/2 的根本区别

对比项 HTTP/2 HTTP/3
传输基础 TCP QUIC,运行在 UDP 之上
安全握手 TCP 后再进行 TLS QUIC 集成 TLS 1.3
多路复用 多个流共享一条 TCP 连接 多个 QUIC 流共享连接
队头阻塞 TCP 丢包可能阻塞同连接上的其他流 单个流丢包通常不会阻塞其他流的数据交付
网络切换 连接通常与 IP/端口绑定 QUIC 具备更好的连接迁移基础

HTTP/3 不保证每个请求都更快。它的收益通常在高延迟、丢包、移动网络和连接建立频繁的场景中更明显。

12.2 Java 26 使用 HTTP/3

java 复制代码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Http3Demo {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_3)
                .connectTimeout(Duration.ofSeconds(5))
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com/"))
                .timeout(Duration.ofSeconds(10))
                .GET()
                .build();

        HttpResponse<String> response = client.send(
                request,
                HttpResponse.BodyHandlers.ofString()
        );

        System.out.println("status=" + response.statusCode());
        System.out.println("actualVersion=" + response.version());
    }
}

HTTP/3 是 Java 26 的正式 API,不需要 --enable-preview

12.3 HTTP/3 不是默认协议

Java 26 的 HttpClient 默认首选版本仍是 HTTP/2。只有显式设置客户端或请求的首选版本为 HTTP_3,客户端才会启用 HTTP/3 协商或发现逻辑。

即使首选 HTTP/3,最终响应也可能使用 HTTP/2 或 HTTP/1.1。因此必须检查:

java 复制代码
response.version()

不要只看到请求配置了 HTTP_3,就认定线上已经走 QUIC。

12.4 请求级别指定 HTTP/3

Java 26 也可以只为某个请求指定首选版本:

java 复制代码
HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
        .version(HttpClient.Version.HTTP_3)
        .GET()
        .build();

这适合渐进式验证,不必让同一个 HttpClient 的全部请求都优先尝试 HTTP/3。

12.5 控制 HTTP/3 发现模式

Java 26 新增 HttpOption.H3_DISCOVERY。JDK 内置客户端支持三种模式:

模式 行为
ANY 由实现选择发现算法,默认模式,可能竞争建立 QUIC 和 TLS/TCP 连接
ALT_SVC 先通过 HTTP/1.1 或 HTTP/2 获取服务器的 Alt-Svc 信息,再使用 HTTP/3
HTTP_3_URI_ONLY 只直接尝试 URI 对应主机和端口的 HTTP/3,不回退

强制只尝试 HTTP/3:

java 复制代码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpOption;
import java.net.http.HttpRequest;

HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
        .version(HttpClient.Version.HTTP_3)
        .setOption(
                HttpOption.H3_DISCOVERY,
                HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY
        )
        .GET()
        .build();

HTTP_3_URI_ONLY 更适合连通性测试和严格协议验证。普通业务如果需要兼容更多网络环境,通常应允许合理回退。

12.6 Java 26 HTTP/3 的边界

  • https 请求可能使用 HTTP/3。
  • JDK 26 内置实现不通过代理使用 HTTP/3;有代理时通常降级,严格模式则失败。
  • 服务端、负载均衡器、CDN、防火墙和网络路径都必须允许 QUIC/UDP。
  • java.net.http 提供的是 HTTP/3 客户端能力,不是 HTTP/3 服务端实现,也不是通用 QUIC API。
  • 需要通过 response.version()、客户端日志、服务端日志和抓包共同验证实际协议。

生产验证时,可按网络类型统计 HTTP/3 成功率、回退率、握手耗时、请求延迟、丢包重传、UDP 被阻断比例,而不是只做一次本地请求。


13. AOT 详解:Java 24---26 如何改善启动与预热

13.1 Java 的 AOT 缓存解决什么问题

传统 HotSpot JVM 启动应用时,需要逐步完成:

text 复制代码
读取 class
  → 解析 class
  → 加载与链接
  → 执行并收集方法画像
  → 识别热点
  → JIT 编译优化

长时间运行的服务通常能摊薄这些成本,但短生命周期任务、Serverless、命令行工具、弹性扩容实例和大型框架应用更关注冷启动与预热。

Project Leyden 的方向是把部分本来在运行时完成的工作提前,并把结果存入 AOT cache,在后续运行时复用。

13.2 Java 24:AOT Class Loading & Linking

JEP 483 在 CDS 基础上继续前进。CDS 主要提前读取和解析类,AOT cache 还可以让应用类以已加载、已链接的状态更快可用。

Java 24 的完整流程分三步。

第一步,使用代表性负载训练并记录配置:

bash 复制代码
java -XX:AOTMode=record \
     -XX:AOTConfiguration=app.aotconfig \
     -cp app.jar com.example.Main

第二步,根据配置组装 AOT cache:

bash 复制代码
java -XX:AOTMode=create \
     -XX:AOTConfiguration=app.aotconfig \
     -XX:AOTCache=app.aot \
     -cp app.jar

第三步,生产运行时加载缓存:

bash 复制代码
java -XX:AOTCache=app.aot \
     -cp app.jar com.example.Main

Windows PowerShell 可以写成一行,或使用反引号进行续行;上面的反斜杠写法适用于 Bash。

13.3 Java 25:命令行简化与方法画像缓存

JEP 514 把常用的训练和组装合并成更简单的流程:

bash 复制代码
java -XX:AOTCacheOutput=app.aot \
     -cp app.jar com.example.Main

随后在生产阶段使用:

bash 复制代码
java -XX:AOTCache=app.aot \
     -cp app.jar com.example.Main

在 PowerShell 中:

powershell 复制代码
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.Main
java -XX:AOTCache=app.aot -cp app.jar com.example.Main

JEP 515 还允许 AOT cache 保存训练运行中的方法执行画像。生产 JVM 启动后可以更快获得分支频率、类型分布等画像信息,从而减少重新收集画像的预热时间。

13.4 Java 26:AOT 对象缓存支持任意 GC

JEP 516 将缓存堆对象的格式与特定 GC 内部对象布局解耦。缓存对象可以从与 GC 无关的中立格式顺序加载,因此包括 ZGC 在内的垃圾收集器都能使用更完整的 AOT cache 能力。

这并不是"ZGC 从 Java 26 才能用 AOT",而是 Java 26 让包含缓存 Java 对象的高级 AOT 优化不再受特定 GC 对象布局限制。

13.5 AOT cache 不是 Native Image

对比项 JDK AOT cache Native Image 思路
输出 .aot 缓存文件,仍配合 JVM 运行 本地可执行文件
运行时 HotSpot 仍然存在,JIT 仍可继续优化 主要依赖构建期闭世界分析与本地代码
兼容性 保留传统 JVM 动态能力更多 反射、动态代理、资源等常需构建期配置
目标 改善启动和预热,同时保留 JVM 运行模型 极致冷启动和较小运行时形态

截至 Java 26,OpenJDK 主线已交付类加载与链接、方法画像、对象缓存等 AOT 优化,但 Project Leyden 页面仍把 AOT 代码编译列为后续工作。不能把 JDK AOT cache 描述成"Java 26 已把业务方法全部提前编译成本地机器码"。

13.6 AOT 使用注意事项

一个 AOT cache 与以下组合绑定:

  • 具体应用及其 classpath、module path 或 JAR。
  • 具体 JDK 版本。
  • 操作系统与 CPU 架构。
  • 训练和生产阶段需要保持一致的关键 JVM 选项。

只要应用依赖、JDK、操作系统镜像、CPU 架构或关键启动参数变化,就应该重新训练和生成缓存。

训练负载也必须具有代表性。只访问健康检查接口,无法覆盖真实业务会加载的类和热点路径。

生产建议使用默认的 AOTMode=auto 容错语义,并通过下面的日志确认缓存是否成功加载:

bash 复制代码
java -XX:AOTMode=auto -Xlog:aot -XX:AOTCache=app.aot \
     -cp app.jar com.example.Main

如果缓存不兼容,auto 模式可以继续在不使用该缓存的情况下启动;on 模式更适合作为严格验证和故障排查手段。


14. 其他重要演进主线

14.1 Scoped Values:从预览到正式

text 复制代码
Java 21:第一次预览
Java 22:第二次预览
Java 23:第三次预览
Java 24:第四次预览
Java 25:正式
Java 26:正式延续

它与虚拟线程、结构化并发共同构成 Project Loom 在应用层的重要能力组合。

14.2 Stream Gatherers:两次预览后正式

text 复制代码
Java 22:第一次预览
Java 23:第二次预览
Java 24:正式

如果项目最低版本仍是 Java 21,公共库不能直接暴露 Gatherer 类型;可以在 Java 24+ 的内部实现中使用,或通过多版本 JAR 管理差异。

14.3 Class-File API:跟上 class 格式演进

text 复制代码
Java 22:第一次预览
Java 23:第二次预览
Java 24:正式

随着 class 文件格式每半年演进,平台自带 API 能降低工具链依赖内部实现的风险。

14.4 垃圾收集器的变化

  • Java 21:分代 ZGC 正式交付。
  • Java 23:分代 ZGC 成为默认模式。
  • Java 24:移除非分代 ZGC,分代 Shenandoah 进入实验阶段。
  • Java 25:Generational Shenandoah 成为产品特性。
  • Java 26:G1 通过减少同步提高吞吐;AOT 对象缓存支持任意 GC。

GC 选择不能只看"新"或"低延迟"标签:

  • G1 适合广泛的通用服务场景。
  • ZGC 更关注超大堆与低停顿。
  • Shenandoah 也面向低停顿,但可用性和支持情况取决于 JDK 发行版。
  • Parallel GC 仍可能适合吞吐优先的批处理任务。

14.5 安全与完整性方向

这几个版本持续限制历史上的高风险通道:

  • 动态加载 Agent 开始告警并为默认禁用做准备。
  • Security Manager 被永久禁用。
  • JNI 使用将逐步受到更明确的本地访问控制。
  • sun.misc.Unsafe 内存访问方法被弃用并告警。
  • Java 26 开始对深反射修改 final 字段发出警告。
  • KEM、KDF、ML-KEM、ML-DSA、PEM 编解码能力逐步进入标准 API。

升级时不仅要编译通过,还要用真实启动参数检查运行时告警,因为很多完整性变化不会在普通源码编译阶段暴露。


15. 从 Java 21 升级到 Java 25 或 Java 26 的实践路线

15.1 第一步:先升级运行时,不急着改源码级别

如果项目当前使用 Java 21,可以先让应用在更高版本 JDK 上运行,同时保持:

xml 复制代码
<maven.compiler.release>21</maven.compiler.release>

或者 Gradle:

groovy 复制代码
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(26)
    }
}

tasks.withType(JavaCompile).configureEach {
    options.release = 21
}

这样可以先暴露运行时兼容问题,再决定哪些模块需要采用新语法和新 API。

15.2 第二步:检查依赖和工具链

重点检查:

  • Maven、Gradle 与插件是否识别目标 JDK。
  • Spring Boot、Tomcat、Netty、Hibernate、Byte Buddy、ASM 是否支持对应 class 版本。
  • APM、覆盖率、Mock、热更新和 Agent 是否依赖动态加载。
  • JNI、本地库和 Unsafe 调用是否触发告警。
  • Docker 基础镜像和 CI 构建节点是否使用同一 JDK 发行版。

15.3 第三步:建立基准,而不是凭感觉判断新特性

虚拟线程至少比较:

  • 固定吞吐下的线程数、内存与 CPU。
  • 固定并发下的 P50、P95、P99 延迟。
  • 数据库连接池、HTTP 连接池和下游限流等待。
  • 超时、取消和异常传播。

AOT 至少比较:

  • 冷启动时间。
  • 首次请求延迟。
  • 达到稳定吞吐所需时间。
  • AOT cache 大小与镜像体积。
  • 不同业务训练覆盖率。

HTTP/3 至少比较:

  • 实际协商版本与回退比例。
  • Wi-Fi、移动网络、高丢包网络下的延迟。
  • UDP 被阻断时的失败和回退行为。
  • CDN、网关、代理和服务端对 QUIC 的支持情况。

15.4 第四步:渐进采用新能力

推荐顺序:

  1. 先升级到受支持的新 JDK 并保持原有代码模型。
  2. 开启运行时日志,处理废弃与完整性告警。
  3. 对 I/O 密集接口灰度虚拟线程。
  4. 使用 Scoped Values 整理只读请求上下文。
  5. 对冷启动敏感应用验证 AOT cache。
  6. 有明确网络收益时再启用 HTTP/3。
  7. 结构化并发和基本类型模式匹配仍按预览特性隔离试点。

15.5 第五步:不要跳过回滚设计

  • 虚拟线程保留平台线程执行器开关。
  • HTTP/3 保留 HTTP/2 回退并记录实际版本。
  • AOT cache 加载失败时允许无缓存启动。
  • 预览特性集中封装,确保下个 JDK 版本可快速调整。
  • GC 参数通过配置管理发布,不要写死在不可变脚本中。

16. 生产环境应该选择 Java 21、25 还是 26

16.1 选择 Java 21

适合:

  • 需要成熟生态和较长验证历史。
  • 正在从 Java 8、11 或 17 迁移。
  • 主要目标是使用虚拟线程、记录模式和 switch 模式匹配。
  • 依赖的框架或中间件尚未完整验证 Java 25/26。

16.2 选择 Java 25

适合:

  • 希望采用新的长期支持基线。
  • 需要正式版 Scoped Values、模块导入、紧凑源码和灵活构造器。
  • 关注 AOT 方法画像、紧凑对象头、JFR 增强与分代 Shenandoah。
  • 能完成一次完整的框架、Agent 和运行时兼容验证。

16.3 选择 Java 26

适合:

  • 明确需要标准 HTTP Client 的 HTTP/3 能力。
  • 希望让高级 AOT 对象缓存与 ZGC 等任意 GC 组合。
  • 愿意采用非 LTS 功能版并遵循更快升级节奏。
  • 希望尽早验证结构化并发第六次预览或基本类型模式第四次预览。

保守的生产策略通常是"Java 21 或 Java 25 作为长期基线,Java 26 用于需要其独有能力的服务"。但真正决定因素应是发行版支持合同、框架兼容性、团队升级能力和实际压测结果。


17. 常见问题

17.1 虚拟线程能完全替代响应式编程吗

不能一概而论。

虚拟线程让同步阻塞代码拥有更好的并发扩展性,能显著降低很多请求型服务的代码复杂度。但事件流、背压、长连接推送和已有完整响应式数据管线仍有自己的价值。

不要为了"使用虚拟线程"把成熟、无阻塞的响应式系统全部重写;也不要在虚拟线程中继续堆叠没有必要的异步回调。

17.2 虚拟线程越多越好吗

虚拟线程本身很便宜,但任务依赖的数据库连接、Socket、堆内对象、请求缓冲区和下游容量并不无限。

应该取消"限制线程数量"的旧习惯,但保留"限制稀缺资源并发量"的工程纪律。

17.3 Java 26 的结构化并发可以直接作为公共库 API 吗

不建议。它仍是第六次预览,Java 25 到 Java 26 就发生了方法名和配置类型调整。

可以在应用内部试点,但最好通过自己的接口封装,不要让 StructuredTaskScope 类型扩散到公共 SDK。

17.4 Java 26 会默认使用 HTTP/3 吗

不会。默认首选仍是 HTTP/2,必须显式设置 HTTP_3。即使设置了,也可能根据服务端与网络条件回退,所以要检查响应的实际版本。

17.5 AOT cache 会让 JVM 不再 JIT 编译吗

不会。AOT cache 主要复用提前读取、解析、加载、链接的类,缓存对象和方法画像等资产。HotSpot 与 JIT 仍然存在,并会继续根据运行时情况优化代码。

17.6 Java 21 的 String Templates 能在 Java 26 中使用吗

不能把它当作现有正式语法。String Templates 只在 Java 21 和 22 中预览,后续提案已撤回,Java 23---26 没有该标准语言特性。

17.7 Java 24 以后 synchronized 就没有问题了吗

不是。Java 24 主要消除了虚拟线程因持有 Java 监视器而发生的常见 carrier pinning。锁竞争、死锁、临界区过大和错误的共享状态设计仍然存在,本地方法或 Foreign Function 阻塞也仍可能造成 pinning。

17.8 可以直接从 Java 21 升到 Java 26 吗

技术上可以跨版本升级,不必逐个版本部署。但验证必须覆盖中间版本累积的移除项、默认行为变化、JVM 参数、框架兼容性、Agent、本地库和序列化行为。


18. 总结

Java 21 到 Java 26 的演进,可以概括为五个方向:

  1. 并发模型更简单。 虚拟线程让同步代码重新具备高吞吐潜力,结构化并发和 Scoped Values 则补齐任务生命周期与上下文传递。
  2. 语言更擅长表达数据。 record、sealed class、记录模式和 switch 模式匹配组合后,领域模型处理更紧凑、更容易被编译器检查。
  3. JVM 更关注冷启动和预热。 AOT cache 从类加载链接扩展到方法画像与跨 GC 对象缓存,同时保留 HotSpot 和 JIT 的动态优化能力。
  4. 标准库覆盖更现代的基础设施。 FFM、Stream Gatherers、Class-File API、KDF、后量子密码算法与 HTTP/3 陆续进入平台。
  5. 平台边界更严格。 Security Manager、32 位端口、Unsafe 内存访问、JNI、动态 Agent 和 final 字段深反射都在被清理或收紧。

如果只选最值得立即掌握的内容:

  • Java 21:虚拟线程、记录模式、switch 模式匹配。
  • Java 22:FFM API、未命名变量。
  • Java 23:了解基本类型模式匹配与预览机制的边界。
  • Java 24:Stream Gatherers、Class-File API、虚拟线程去 pinning、AOT 起点。
  • Java 25:Scoped Values、正式语言改进、AOT 方法画像。
  • Java 26:HTTP/3、任意 GC 的 AOT 对象缓存,以及仍处于预览阶段的结构化并发。

真正稳妥的升级方式,不是看到新语法就全面重写,而是先升级运行时、建立可重复基准、处理兼容告警,再用灰度和回滚机制逐步采用能解决实际问题的新能力。


19. 官方资料

19.1 各版本特性列表

19.2 核心 JEP

19.3 Java 26 开发文档

相关推荐
我星期八休息34 分钟前
Linux I/O多路转接—epoll
java·linux·运维·服务器·开发语言·jvm·算法
学长毕业设计1 小时前
基于springboot的云南中草药知识普及管理网站的设计与开发(源码+文档+讲解视频)
java·spring boot·后端
大模型码小白1 小时前
AI 提示词专栏:使用系统指令(System Prompt)实现全局约束
java·大数据·运维·开发语言·人工智能·python·prompt
cxoptics1 小时前
蓝宝石的轴向在窗片和偏振片中起的作用
java
wuminyu1 小时前
Java FFM处理网络读写事件源码剖析
java·linux·c语言·jvm·c++
小江的记录本2 小时前
【AI Agent】《2026年9月 AI Agent全栈开发技术选型专项面试宝典》
java·spring boot·python·spring·ai·面试·ai编程
梦梦代码精2 小时前
《LikeShop全产品技术硬核拆解:ThinkPHP8+Vue3+UniApp架构,私有化部署与二次开发实战指南》
java·低代码·系统架构·php·开源软件
一木 之林2 小时前
C/C++ 面向对象编程(OOP)(进阶)
java·c语言·c++
摇滚侠3 小时前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 2
java·spring boot·笔记