从 21 迁移到 25
学完你要能自己回答
- 21 → 25 哪些 API 从预览转正?哪些仍要 --enable-preview?
- 结构化并发的 API 在 25 改了什么?21 的代码为什么编译不过?
- 紧凑对象头与分代 Shenandoah 转正后,为什么默认行为没变?
- 32 位 x86 移除与 AOT 缓存对升级分别意味着什么?
版本坐标:class 65 → 6901
JDK 21 的 class 文件版本是 65,JDK 25 是 69。先用目标 JDK 确认运行时与编译工具一致,再用 --release 25 编译;--release 同时约束语法、字节码版本和标准库 API 快照。
java -version
javac -version
openjdk version "25" 2025-09-16 LTS OpenJDK Runtime Environment Corretto-25.0.0.36.2 (build 25+36-LTS) OpenJDK 64-Bit Server VM Corretto-25.0.0.36.2 (build 25+36-LTS, mixed mode, sharing)
| 版本 | class 文件 | 默认 GC | 迁移时最值得注意的变化 |
|---|---|---|---|
| JDK 21 | 65 | G1 | 虚拟线程正式;结构化并发 / Scoped Values 预览 |
| JDK 25 | 69 | G1 | Scoped Values、模块导入、紧凑源文件、灵活构造器体正式;结构化并发仍预览 |
| JDK 25 移除 | — | — | 32 位 x86 端口(JEP 503) |
| JDK 25 产品但默认关闭 | — | — | 紧凑对象头(JEP 519)、分代 Shenandoah(JEP 521) |
javac --release 21 -d out21 ModuleImportDemo.java
# error: module imports are not supported in -source 21
ModuleImportDemo.java:1: error: module imports are not supported in -source 21
import module java.base;
^
(use -source 25 or higher to enable module imports)
1 error
--release 25 编译出的 class 在 21 上会抛 UnsupportedClassVersionError。反过来,21 编译的 class 通常能在 25 上运行,但预览 class 除外。
API:转正、改名与移除02
先按「21 的状态 → 25 的状态」列一张表,再处理不兼容改签名。最容易踩的是 ScopedValue 的便捷方法与结构化并发的整套 API。
| 特性 / API | JDK 21 | JDK 25 | 迁移动作 |
|---|---|---|---|
| ScopedValue | 预览 | 正式 | 去掉 --enable-preview;改用 where(...).run/call |
runWhere / callWhere / getWhere | 存在 | 移除 | 改写成 Carrier 链式调用 |
orElse(null) | 可传 null | 抛 NPE | 改用显式默认值或 orElseThrow |
| 结构化并发 | 预览,ShutdownOnFailure | 第五次预览,open/Joiner | 重写 API;仍加 --enable-preview |
| 模块导入 | 不支持 | 正式 | 可用 import module;去掉预览参数 |
| 紧凑源文件 | 预览(simple source) | 正式 | 可去掉外层类;注意 IO 静态导入变化 |
| 灵活构造器体 | 预览 | 正式 | 可在 super 前校验;检查字段初始化器 |
import java.util.concurrent.*;
public class OldApi {
void fetch() throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(() -> "a");
scope.join().throwIfFailed();
}
}
}
OldApi.java:5: error: cannot find symbol
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
^
symbol: class ShutdownOnFailure
location: interface StructuredTaskScope
Note: OldApi.java uses preview features of Java SE 25.
Note: Recompile with -Xlint:preview for details.
1 error
new ShutdownOnFailure()→open()或open(Joiner.allSuccessfulOrThrow())。join().throwIfFailed()→join();失败直接抛FailedException。ShutdownOnSuccess.result()→open(Joiner.anySuccessfulResultOrThrow()).join()。joinUntil(Instant)→open(joiner, cfg -> cfg.withTimeout(Duration))。
运行时:转正不等于默认开启03
紧凑对象头(JEP 519)在 25 从实验特性转为产品特性,但 JEP 519 明确把「改变默认对象头布局」列为非目标。本机默认值仍是 false,必须显式开启。
java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders
bool UseCompactObjectHeaders = false {product lp64_product} {default}
加 -XX:+UseCompactObjectHeaders 后,同一命令会显示 true {command line};不需要 -XX:+UnlockExperimentalVMOptions,因为 25 已将其转为产品特性。分代 Shenandoah(JEP 521)同样只是从实验转为产品,默认仍是单代 satb。
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational \
-XX:+PrintFlagsFinal -version | grep ShenandoahGCMode
ccstr ShenandoahGCMode = generational {product} {command line}
AOT 方面,JEP 514 简化了创建 AOT 缓存的命令,JEP 515 把训练运行的方法 profile 放进缓存,改善启动与预热。三步仍是 record → create → run,收益主要在启动阶段,不是峰值吞吐。
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -cp app.jar AotApp
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -cp app.jar
java -XX:AOTCache=app.aot -cp app.jar AotApp
aot app running
- 32 位 x86 端口在 25 移除(JEP 503):不再提供 32 位 x86 运行时;检查 native agent、JNI 库和容器镜像。
System.setSecurityManager与Thread.stop在 25 仍抛UnsupportedOperationException。-XX:-UseCompressedClassPointers在 25 已弃用并会警告;紧凑对象头依赖它,关闭后紧凑对象头也会被禁用。
预览:25 还有什么,为什么不能跨版本04
JDK 25 仍有多项预览/孵化特性。它们不是「beta 正式版」:API 可能改名、改签名甚至撤回,class 文件也会被标记为预览。
| JEP | 特性 | 25 状态 |
|---|---|---|
| 505 | Structured Concurrency (Fifth Preview) | 预览 |
| 502 | Stable Values | 预览 |
| 507 | Primitive Types in Patterns, instanceof, and switch | 第三次预览 |
| 470 | PEM Encodings of Cryptographic Objects | 预览 |
| 508 | Vector API | 第十次孵化 |
| 506 / 511 / 512 / 513 | Scoped Values / 模块导入 / 紧凑源文件 / 灵活构造器体 | 正式 |
| 519 / 521 | 紧凑对象头 / 分代 Shenandoah | 产品特性,默认关闭 |
javac --release 25 --enable-preview -d out ScopedStructuredDemo.java
java --enable-preview -cp out ScopedStructuredDemo
预览 class 的 minor version 是 65535。只在编译时加 --enable-preview、却忘记运行时加,会得到下面的错误;把 21 的预览 class 放到 25 上同样不行。
Error: LinkageError occurred while loading main class ScopedStructuredDemo java.lang.UnsupportedClassVersionError: Preview features are not enabled for ScopedStructuredDemo (class file version 69.65535). Try running with '--enable-preview'
- 编译和运行两端都要
--enable-preview,且运行时 JDK 版本必须与编译时一致。 - 21 的预览 class 是 65.65535,25 只识别 69.65535;升级后必须重新编译。
--enable-preview本身不会把普通 class 标成 65535;只有实际使用预览特性才会。- 预览代码应隔离在适配层后面,不要把预览类型暴露到公共 API。
升级检查清单05
- 盘点所有运行时:本地、CI、容器、Kubernetes、构建插件,统一到 25。
- 用
--release 25编译,开启-Xlint:removal;先让编译警告可见。 - 重写结构化并发:
open()+Joiner;确认仍用--enable-preview。 - 替换 ScopedValue 的
runWhere/callWhere/getWhere;检查orElse(null)。 - 模块导入去掉预览参数;处理 List / Date 等同名类歧义。
- 紧凑源文件检查 package 与 IO 静态导入;需要 --release 时改用 javac。
- A/B 评估紧凑对象头与分代 Shenandoah;确认不是 32 位 x86 并回归 native 依赖。
- 隔离预览特性,跑完整测试、压测与回滚演练。
迁移完成的标准不是「能启动」,而是「启动参数干净、没有依赖预览 class、默认值与预期一致、测试与压测通过、回滚路径明确」。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| 32 位 x86 端口 | 25 移除 | JEP 503;不再提供 32 位 x86 运行时。 |
| Scoped Values | 25 正式 | JEP 506;runWhere 等已移除。 |
| 结构化并发 | 25 第五次预览 | JEP 505;open/Joiner,仍需 --enable-preview。 |
| 模块导入 | 25 正式 | JEP 511;不替代 requires。 |
| 紧凑源文件 | 25 正式 | JEP 512;不能有 package 声明。 |
| 灵活构造器体 | 25 正式 | JEP 513;早期构造上下文有限制。 |
| 紧凑对象头 | 25 产品,默认关闭 | JEP 519;JEP 450 引入,需显式开启。 |
| 分代 Shenandoah | 25 产品,默认 satb | JEP 521;需显式指定 ShenandoahGCMode=generational。 |
| AOT 命令与方法 profile | 25 正式 | JEP 514 / 515;改善启动与预热。 |
| Security Manager / Thread.stop | 25 抛 UOE | JDK 24 起 Security Manager 永久禁用。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。