《Java 高级编程》附录 C 版本特性矩阵

按 JDK 8/11/17/21/25 五个基线,把 records、sealed、模式匹配、虚拟线程、ScopedValue、FFM、StructuredTaskScope、JEP 491 与 JEP 472 的状态整理成矩阵,逐行标注正式、预览、移除及对 libraryd 的影响,并给出本机验证命令与常见版本误记。

读表方法

这张矩阵回答三个问题:某个特性从哪个版本开始能用、在 JDK 25 是什么状态、libraryd 要不要依赖它。

状态只有六种:

  • 正式:生产可用,不需要额外编译或运行参数。
  • 预览:javac --release N --enable-preview 与 java --enable-preview 同时开启;API 可能在下个版本变化。
  • 孵化:独立的 incubator 模块,需要 --add-modules,比预览更不稳定。
  • 实验:需要 -XX:+UnlockExperimentalVMOptions 一类的开关,不保证长期存在。
  • 告警:当前可用,但运行时打印警告,后续版本可能拒绝。
  • 移除:该版本不再提供。

表中的「—」表示该版本没有这个特性。JDK 8/11/17/21/25 是本书关心的五个基线,其中 8、11、17、21、25 都是 LTS。发布时间按 OpenJDK GA 日期:

版本发布时间定位
JDK 82014-03-18Lambda 与 Stream,最早的长期支持基线
JDK 112018-09-25模块化之后首个 LTS
JDK 172021-09-14records、sealed、强封装
JDK 212023-09-19虚拟线程、模式匹配
JDK 242025-03-18JEP 491 解决监视器 pinning,JEP 472 引入原生访问告警
JDK 252025-09-16本卷基线,ScopedValue 与模块导入正式

语言特性矩阵

特性811172125对 libraryd 的影响
Lambda、Stream、默认方法正式正式正式正式正式Book/Loan 列表处理;热路径仍用循环
var 局部变量—正式(10 起)正式正式正式仓储方法内部少写泛型类型,签名不变
文本块——正式(15 起)正式正式测试里的 SQL、JSON 固件更易读
switch 表达式——正式(14 起)正式正式ErrorCode 到 HTTP 状态的映射
instanceof 模式——正式(16 起)正式正式异常转换少一次强制类型转换
records——正式(16 起)正式正式Book、Member、Loan、Page<T>
sealed 类/接口——正式正式正式LoanResult、LoanState 的封闭层次
switch 模式匹配——预览(17–20)正式正式LoanState 穷尽分支,编译器检查遗漏
record 模式———正式(19 起预览)正式case Overdue(var due, var days) 直接解构
未命名模式 _———预览正式(22 起)忽略不关心的 record 组件
基元模式————预览(第三次)不在生产代码使用,继续显式装箱或拆分分支
模块导入声明————正式小工具和示例可以少写 import,主工程仍显式导入
紧凑源文件与实例 main————正式单文件脚本、教学示例;不替代 Maven 模块
灵活构造器体————正式在 super(...) 前做参数校验,领域基类可用

libraryd 的类型改造集中在 17 和 21:17 让 Book、Loan 成为 record,让 LoanResult 成为 sealed;21 让 LoanState 的 switch 穷尽。25 的模块导入、紧凑源文件适合示例,不应该为了少写几行 import 改变主工程结构。

运行时与 API 矩阵

特性811172125对 libraryd 的影响
虚拟线程———正式(JEP 444)正式Javalin 请求处理、DashboardService 子任务
ScopedValue———预览(JEP 446)正式(JEP 506)替代 ThreadLocal 传递 requestId、会员上下文
StructuredTaskScope———预览(JEP 453)预览(JEP 505,第五次)仪表盘三路查询;编译运行都要 --enable-preview
FFM(java.lang.foreign)——孵化(JEP 412)预览(JEP 442)正式(JEP 454,22 起)调用 libisbn;restricted method 需要 native access
synchronized 与 pinning无虚拟线程无虚拟线程无虚拟线程会 pin不再 pin(JEP 491,24 起)不需要仅为规避 pinning 重写 synchronized
JNI/FFM 原生访问告警————告警(JEP 472,24 起)启动加 --enable-native-access=ALL-UNNAMED
紧凑对象头————正式但默认关闭(24 实验)可用 -XX:+UseCompactObjectHeaders,先测量再开
Security Manager正式正式弃用(JEP 411)弃用永久禁用(JEP 486,24 起)不再依赖策略文件,权限模型移到应用层
32 位 x86 端口支持支持支持弃用(JEP 449)移除(JEP 503)构建机与基础镜像确认是 x86_64/AArch64
Java EE、CORBA 模块内置移除(JEP 320)移除移除移除用 Jakarta 替代;libraryd 不依赖 JAXB/CORBA
HTTP Client—正式(JEP 321)正式正式正式集成测试和外部调用,不必再带 Apache HttpClient
JFR商业版(Oracle JDK)开源(JEP 328)正式正式正式生产采样与故障复盘

这张表里最容易出错的是「版本归属」和「状态」两列。下面按版本解释关键变化,再给出迁移路径。

JDK 8:迁移基线

JDK 8 引入 Lambda、Stream、默认方法、Optional、java.time、CompletableFuture,并用 Metaspace 取代 PermGen。它没有 records、sealed、模式匹配和虚拟线程。

如果 libraryd 还在 8 上,Book、Loan 只能写成可变或不可变类,LoanResult 只能用抽象类加子类模拟封闭层次,DashboardService 只能用线程池加 Future。本卷所有写法都以 25 为目标,8 只作为迁移起点。

JDK 11:模块化与 HTTP Client

JDK 11 的关键变化:

  • var 从 10 起可用,局部变量可以省略显式类型。
  • 新的 java.net.http.HttpClient 正式,替代 HttpURLConnection。
  • 单文件源码启动 java Hello.java,适合验证小片段。
  • 移除 Java EE 与 CORBA 模块(JEP 320),javax.xml.bind 等包不再随 JDK 提供。
  • ZGC 与 Epsilon 作为实验 GC 出现,JFR 在 OpenJDK 中开源(JEP 328)。

libraryd 的影响主要在构建与测试:HttpClient 可以替代测试里的第三方 HTTP 客户端;如果代码用了 JAXB,升级到 11 必须补 Jakarta 依赖。模块化本身在 11 还不是强制,真正踩坑通常在 17 的强封装。

JDK 17:records、sealed 与强封装

JDK 17 是本书类型改造的起点:

  • records 在 16 正式(JEP 395),17 可以直接用。
  • sealed 类/接口在 17 正式(JEP 409)。
  • instanceof 模式在 16 正式(JEP 394)。
  • 文本块在 15 正式,switch 表达式在 14 正式。
  • JDK 内部 API 强封装(JEP 403),反射访问 java.base 内部字段默认失败。
  • macOS/AArch64 端口正式(JEP 391),Apple Silicon 上不再依赖 Rosetta。

libraryd 在 17 上可以把 Book、Member、Loan、Page<T> 写成 record,把 LoanResult 写成 sealed interface。代价是深反射工具开始遇到 InaccessibleObjectException,需要 --add-opens 或模块 opens。附录 B 给了处理模板。

JDK 21:虚拟线程与模式匹配

JDK 21 是本卷并发章节的基线:

  • 虚拟线程正式(JEP 444),Executors.newVirtualThreadPerTaskExecutor()、Thread.ofVirtual() 可用。
  • switch 模式匹配正式(JEP 441),record 模式正式(JEP 440)。
  • 顺序集合(JEP 431)让 List、Deque 等集合共享 SequencedCollection 的 getFirst、addLast、reversed 操作。
  • 分代 ZGC 正式(JEP 439)。
  • ScopedValue、StructuredTaskScope、FFM、未命名模式、字符串模板都是预览。
  • 32 位 x86 端口弃用(JEP 449),为 25 的移除做铺垫。

libraryd 在 21 上可以让每个 HTTP 请求跑在虚拟线程上,LoanState 用穷尽 switch 表达,DashboardService 可以用 StructuredTaskScope 原型验证取消和错误传播。注意这些原型在 21 和 25 的 API 形态不同,不能直接照抄。

JDK 25:ScopedValue 正式与预览边界

JDK 25 是本卷的编译与运行基线,关键变化:

  • ScopedValue 正式(JEP 506),ScopedValue.where(KEY, value).run(...) 可以替代请求上下文里的 ThreadLocal。
  • 模块导入声明正式(JEP 511),import module java.base; 合法。
  • 紧凑源文件与实例 main 正式(JEP 512),单文件程序可以不写类声明。
  • 灵活构造器体正式(JEP 513),允许在 super(...) 或 this(...) 之前执行校验语句。
  • 基元模式仍是第三次预览(JEP 507),不能当正式特性。
  • StructuredTaskScope 仍是第五次预览(JEP 505),API 是 open(Joiner),不是 JDK 21 的 ShutdownOnFailure 构造器。
  • 紧凑对象头正式(JEP 519),但 -XX:+UseCompactObjectHeaders 默认关闭;本机 PrintFlagsFinal 显示 false。
  • 32 位 x86 端口移除(JEP 503)。
  • JNI/FFM 原生访问继续告警(JEP 472),未授权调用在后续版本可能被拒绝。

libraryd 在 25 上的落地建议:

  • 请求上下文从 ThreadLocal 迁到 ScopedValue,但连接池、限流器仍然按线程/任务边界管理。
  • 仪表盘继续用 StructuredTaskScope 时,构建脚本要显式加预览参数;不要把它写进对外 API。
  • FFM 调用 libisbn 的启动脚本统一加 --enable-native-access=ALL-UNNAMED,不要等告警变成拒绝。
  • synchronized 不需要为了虚拟线程改写;剩下的 pin 风险集中在本地方法和 FFM 调用。
  • 紧凑对象头先在基准测试里开,别在生产默认打开。

JDK 25 的代码形态

ScopedValue 在 25 正式之后,请求上下文的写法可以收敛成下面这样:

public final class RequestContext {
    public record Member(String id, String name) { }

    public static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
    public static final ScopedValue<Member> MEMBER = ScopedValue.newInstance();

    public static void run(String requestId, Member member, Runnable handler) {
        ScopedValue.where(REQUEST_ID, requestId)
                .where(MEMBER, member)
                .run(handler);
    }

    public static void main(String[] args) {
        run("req-42", new Member("M1", "Leeting Yan"),
                () -> System.out.println(REQUEST_ID.get() + "/" + MEMBER.get().id()));
    }
}

本机输出:

req-42/M1

绑定只在 run 的动态作用域内有效,没有 set、remove,也不会像 ThreadLocal 那样在线程池里残留。作用域内创建的子任务可以读取绑定,这正是它和 StructuredTaskScope 配合的场景。libraryd 可以在请求入口用 ScopedValue.where(...).run(handler) 包住路由处理,审计日志直接读 REQUEST_ID,不需要在每个方法参数里传。

预览特性的参数组合

场景编译运行
普通 JDK 25 代码javac --release 25java
StructuredTaskScopejavac --release 25 --enable-previewjava --enable-preview
基元模式javac --release 25 --enable-previewjava --enable-preview
FFM 调用javac --release 25java --enable-native-access=ALL-UNNAMED
模块化应用javac --release 25 --module-source-path srcjava --module-path mods -m 模块/主类

--release 25 与 --enable-preview 必须同时出现;只给 java --enable-preview 而编译时没开,类文件里没有预览标记,运行会报错。反过来,编译开了预览但运行没开,同样会拒绝加载。

跨版本迁移路径

步骤动作验证
8 → 11移除 JAXB/CORBA 依赖,替换为 Jakarta;引入 HttpClientjdeps --jdk-internals、全量测试
11 → 17领域类型改 record,结果类型改 sealed;处理强封装--add-opens 清单、反射测试
17 → 21请求处理改虚拟线程;switch 改模式匹配压测连接池、线程 dump
21 → 25ThreadLocal 迁 ScopedValue;补 native access 参数JFR、jcmd Thread.dump_to_file
跨过 25检查 32 位构建机;确认 StructuredTaskScope 仍是预览CI 矩阵、java -version

迁移不要一次跨三个版本。每一跳都跑一次全量构建和集成测试,尤其是反射、序列化和原生调用这三类代码,它们对版本变化最敏感。

容易记错的版本事实

  1. records 是 16 正式,不是 17。 17 只是 LTS,不是首次引入。
  2. sealed 是 17 正式。 与 records 同属 17 的语言改造,但引入版本不同。
  3. switch 模式匹配和 record 模式是 21 正式。 17 只有预览。
  4. ScopedValue 是 25 正式,不是 21。 21 只是预览。
  5. StructuredTaskScope 在 25 仍是预览。 第五次预览,且 API 已改成 open(Joiner)。
  6. 基元模式在 25 仍是预览。 不要写进生产代码,也不要在文章里标成正式。
  7. JEP 491 是 24 的 JEP,不是 25。 效果延续到 25:synchronized 不再 pin 虚拟线程。
  8. FFM 是 22 正式。 21 还是预览;25 对 restricted method 告警。
  9. 紧凑对象头在 25 正式但默认关闭。 需要显式 -XX:+UseCompactObjectHeaders。
  10. 32 位 x86 在 25 移除。 21 只是弃用。

本机验证

以下命令都在本机 Temurin 25.0.5 上执行过,输出为原文节选。

ScopedValue 不加预览参数即可编译运行:

javac --release 25 -d out ScopedProbe.java
java -cp out ScopedProbe
scoped=req-1

StructuredTaskScope 不加预览参数会直接拒绝:

Probe.java:1: 错误: StructuredTaskScope 是预览 API,默认情况下处于禁用状态。
import java.util.concurrent.StructuredTaskScope;
                           ^
  (请使用 --enable-preview 以启用预览 API)

基元模式同样需要预览:

PrimitiveProbe.java:4: 错误: 基元模式 是预览功能,默认情况下禁用。
            case int i -> "int " + i;
                 ^

灵活构造器体在 21 不合法、在 25 合法:

javac --release 21 -d out21 FlexibleCtor.java
javac --release 25 -d out25 FlexibleCtor.java
FlexibleCtor.java:12: 错误: -source 21 中不支持 灵活构造器
            super(x);

JEP 491 的验证方式是 JFR:让虚拟线程在 synchronized 块里 Thread.sleep(200),用 -XX:StartFlightRecording 录制,再查 jdk.VirtualThreadPinned。本机事件类型存在,但捕获到 0 条事件;这与「JDK 25 中 synchronized 不再 pin」一致。

FFM 的告警原文见附录 D。紧凑对象头的默认值可以用下面的命令确认:

java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders
bool UseCompactObjectHeaders = false {product lp64_product} {default}

小结

  • JDK 8 是迁移起点,没有 records、sealed、模式匹配和虚拟线程。
  • JDK 11 带来 var、HttpClient、单文件启动,并移除 Java EE/CORBA 模块。
  • JDK 17 带来 records、sealed、instanceof 模式和强封装,是 libraryd 类型改造的起点。
  • JDK 21 带来虚拟线程、switch 模式匹配和 record 模式,是并发章节的基线。
  • JDK 25 让 ScopedValue、模块导入、紧凑源文件、灵活构造器体正式,但 StructuredTaskScope 与基元模式仍是预览。
  • 版本事实按「首次正式」记,不要按 LTS 记;JEP 491 属于 24,FFM 属于 22。
  • 预览 API 必须同时加 --release 25 --enable-preview 和 --enable-preview,少一个都不行。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发