JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · MIGRATION · 21 → 25

从 21 迁移到 25

JDK 25 · 速查主题 06 / 06 · 预计 30 分钟
进阶30 分钟JEP 503JEP 511JEP 519JEP 521JEP 514JEP 515
一句话21 → 25 的升级难点不在语法,而在「预览转正」与「默认值没变」:Scoped Values 与模块导入去掉预览参数,结构化并发换了 API 却仍是预览,紧凑对象头与分代 Shenandoah 转正但默认关闭,32 位 x86 端口被移除。

学完你要能自己回答

  1. 21 → 25 哪些 API 从预览转正?哪些仍要 --enable-preview?
  2. 结构化并发的 API 在 25 改了什么?21 的代码为什么编译不过?
  3. 紧凑对象头与分代 Shenandoah 转正后,为什么默认行为没变?
  4. 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 2165G1虚拟线程正式;结构化并发 / Scoped Values 预览
JDK 2569G1Scoped 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。

特性 / APIJDK 21JDK 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))。
结构化并发在 25 仍是预览,不要因为「换了 API」就当成正式能力。生产代码要继续隔离并锁定 JDK 版本。

运行时:转正不等于默认开启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 已弃用并会警告;紧凑对象头依赖它,关闭后紧凑对象头也会被禁用。
「转正」描述的是支持级别,不是默认值。升级时不要假设紧凑对象头或分代 Shenandoah 已经生效;用 PrintFlagsFinal 和 GC 日志确认。

预览:25 还有什么,为什么不能跨版本04

JDK 25 仍有多项预览/孵化特性。它们不是「beta 正式版」:API 可能改名、改签名甚至撤回,class 文件也会被标记为预览。

JEP特性25 状态
505Structured Concurrency (Fifth Preview)预览
502Stable Values预览
507Primitive Types in Patterns, instanceof, and switch第三次预览
470PEM Encodings of Cryptographic Objects预览
508Vector API第十次孵化
506 / 511 / 512 / 513Scoped 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。
模块导入、Scoped Values 等在 25 已正式,不再需要 --enable-preview。升级后清理这些过时参数,避免团队误以为仍在预览。

升级检查清单05

  1. 盘点所有运行时:本地、CI、容器、Kubernetes、构建插件,统一到 25。
  2. 用 --release 25 编译,开启 -Xlint:removal;先让编译警告可见。
  3. 重写结构化并发:open() + Joiner;确认仍用 --enable-preview。
  4. 替换 ScopedValue 的 runWhere/callWhere/getWhere;检查 orElse(null)。
  5. 模块导入去掉预览参数;处理 List / Date 等同名类歧义。
  6. 紧凑源文件检查 package 与 IO 静态导入;需要 --release 时改用 javac。
  7. A/B 评估紧凑对象头与分代 Shenandoah;确认不是 32 位 x86 并回归 native 依赖。
  8. 隔离预览特性,跑完整测试、压测与回滚演练。

迁移完成的标准不是「能启动」,而是「启动参数干净、没有依赖预览 class、默认值与预期一致、测试与压测通过、回滚路径明确」。

不要直接 8 → 25 或 11 → 25 跳过多级检查点。每个 LTS 都移除了不同的东西,逐级升级更容易定位依赖与反射问题。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
32 位 x86 端口25 移除JEP 503;不再提供 32 位 x86 运行时。
Scoped Values25 正式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 引入,需显式开启。
分代 Shenandoah25 产品,默认 satbJEP 521;需显式指定 ShenandoahGCMode=generational。
AOT 命令与方法 profile25 正式JEP 514 / 515;改善启动与预热。
Security Manager / Thread.stop25 抛 UOEJDK 24 起 Security Manager 永久禁用。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. 从 21 升到 25,class 文件版本与编译参数怎么变?
class 文件从 65 升到 69;用 javac --release 25 编译。21 编译的普通 class 通常能在 25 运行,但 21 的预览 class(65.65535)不能。
02. 结构化并发在 25 是正式的吗?21 的代码能直接编译吗?
不是正式,仍是第五次预览。21 的 ShutdownOnFailure / ShutdownOnSuccess 已移除,必须改用 open() + Joiner,并继续加 --enable-preview。
03. 紧凑对象头与分代 Shenandoah 转正后,默认开启了吗?
都没有。JEP 519 明确不改变默认对象头布局,25 默认仍是 96/128 位,需要 -XX:+UseCompactObjectHeaders;分代 Shenandoah 默认仍是 satb,需要显式指定 ShenandoahGCMode=generational。
04. 32 位 x86 移除对升级有什么实际影响?
25 不再提供 32 位 x86 运行时。依赖 32 位 x86 JVM 的部署、native agent、JNI 库或容器镜像必须换成 64 位;否则无法启动或加载本地库。
05. 预览 class 为什么不能跨版本?
预览 class 的 minor version 是 65535,且只对编译它的 JDK 版本有效。25 只识别 69.65535,21 的 65.65535 会抛 UnsupportedClassVersionError;升级后必须重新编译。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。