读表方法
这张矩阵回答三个问题:某个特性从哪个版本开始能用、在 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 8 | 2014-03-18 | Lambda 与 Stream,最早的长期支持基线 |
| JDK 11 | 2018-09-25 | 模块化之后首个 LTS |
| JDK 17 | 2021-09-14 | records、sealed、强封装 |
| JDK 21 | 2023-09-19 | 虚拟线程、模式匹配 |
| JDK 24 | 2025-03-18 | JEP 491 解决监视器 pinning,JEP 472 引入原生访问告警 |
| JDK 25 | 2025-09-16 | 本卷基线,ScopedValue 与模块导入正式 |
语言特性矩阵
| 特性 | 8 | 11 | 17 | 21 | 25 | 对 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 矩阵
| 特性 | 8 | 11 | 17 | 21 | 25 | 对 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 25 | java |
| StructuredTaskScope | javac --release 25 --enable-preview | java --enable-preview |
| 基元模式 | javac --release 25 --enable-preview | java --enable-preview |
| FFM 调用 | javac --release 25 | java --enable-native-access=ALL-UNNAMED |
| 模块化应用 | javac --release 25 --module-source-path src | java --module-path mods -m 模块/主类 |
--release 25 与 --enable-preview 必须同时出现;只给 java --enable-preview 而编译时没开,类文件里没有预览标记,运行会报错。反过来,编译开了预览但运行没开,同样会拒绝加载。
跨版本迁移路径
| 步骤 | 动作 | 验证 |
|---|---|---|
| 8 → 11 | 移除 JAXB/CORBA 依赖,替换为 Jakarta;引入 HttpClient | jdeps --jdk-internals、全量测试 |
| 11 → 17 | 领域类型改 record,结果类型改 sealed;处理强封装 | --add-opens 清单、反射测试 |
| 17 → 21 | 请求处理改虚拟线程;switch 改模式匹配 | 压测连接池、线程 dump |
| 21 → 25 | ThreadLocal 迁 ScopedValue;补 native access 参数 | JFR、jcmd Thread.dump_to_file |
| 跨过 25 | 检查 32 位构建机;确认 StructuredTaskScope 仍是预览 | CI 矩阵、java -version |
迁移不要一次跨三个版本。每一跳都跑一次全量构建和集成测试,尤其是反射、序列化和原生调用这三类代码,它们对版本变化最敏感。
容易记错的版本事实
- records 是 16 正式,不是 17。 17 只是 LTS,不是首次引入。
- sealed 是 17 正式。 与 records 同属 17 的语言改造,但引入版本不同。
- switch 模式匹配和 record 模式是 21 正式。 17 只有预览。
- ScopedValue 是 25 正式,不是 21。 21 只是预览。
- StructuredTaskScope 在 25 仍是预览。 第五次预览,且 API 已改成
open(Joiner)。 - 基元模式在 25 仍是预览。 不要写进生产代码,也不要在文章里标成正式。
- JEP 491 是 24 的 JEP,不是 25。 效果延续到 25:
synchronized不再 pin 虚拟线程。 - FFM 是 22 正式。 21 还是预览;25 对 restricted method 告警。
- 紧凑对象头在 25 正式但默认关闭。 需要显式
-XX:+UseCompactObjectHeaders。 - 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,少一个都不行。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。