news 2026/11/3 19:07:16

11-从 JDK 8 迁移到 JDK 25 / 26 实战:风险评估与落地清单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
11-从 JDK 8 迁移到 JDK 25 / 26 实战:风险评估与落地清单

一、先决定升到哪:2026 年的目标版本

不要追非 LTS,也不要一步跳到最新。2026 年的现实选择:

目标定位建议
JDK 11老 LTS,Oracle 免费更新已于 2023 年结束只当跳板,不停留
JDK 17LTS,Oracle 免费(NFTC)更新 2026-09-30 到期仍是合格的中间站
JDK 21LTS,虚拟线程 GA,支持到 2031 年已在 21 上的可以不急
JDK 25当前 LTS,支持到 2033 年生产落点首选
JDK 26非 LTS,2026-03 GA、2026-09 已停止更新只做验证 / 试点,别当终点
JDK 27非 LTS,2026-09-15 GA,支持到 2027-03只适合追 6 个月节奏的团队

建议路线:老系统8 → 11 → 17 → 21 → 25分阶段走,每步都有测试兜底;站稳 25 之后,再单独跑一轮 26 的冒烟,验证 HTTP/3、AOT 缓存这类新能力。

为什么现在是必须动的时候(三条现实压力):

  1. JDK 8 / 11 的免费更新早已结束,安全补丁要商业订阅;
  2. JDK 17 的免费更新 2026-09-30 到期,停在 17 的团队要么升级,要么付费;
  3. Oracle JDK 21 的免费 NFTC 许可在 2026-09 到期,之后的更新走 OTN 许可。

    具体政策以你用的发行版为准(Adoptium / Amazon Corretto / Azul / 腾讯 Kona 各有自己的节奏),但方向一致:往 25 LTS 迁是成本最低的一条路。


二、迁移第一步:静态评估

动手前先用工具扫一遍代码,量化风险:

# 1. jdeps:分析依赖与对 JDK 内部 API 的使用jdeps --jdk-internals --multi-release25myapp.jar# 输出会列:用了哪些 JDK 内部 API(如 sun.misc.Unsafe、com.sun.*)# 2. jdeprscan:看废弃 API 使用(目标版本填 25 或 26)jdeprscan--release25myapp.jar# 3. 26 上新增的两个"照妖镜",建议在 25 阶段就先跑一遍java--sun-misc-unsafe-memory-access=warn-jarmyapp.jar# 谁还在用 Unsafejava--illegal-final-field-mutation=deny-jarmyapp.jar# 谁还在反射改 final 字段# 4. Spring Boot 项目用官方迁移指南 + spring-boot-properties-migrator 找配置项变化

重点看四类问题:

  • 内部 API 使用:jdeps --jdk-internals报告的,必须换等价公共 API 或加--add-opens临时放行。
  • 被移除的模块:JDK 11 移除 JAXB(javax.xml.bind)、JAX-WS、CORBA、JAF、JavaFX、JNLP;JDK 15 移除 Nashorn;JDK 26 移除 Applet API。用到就补第三方依赖(jakarta.xml.bind:jakarta.xml.bind-api+ 实现)。
  • 终将移除的 API:sun.misc.Unsafe的内存访问方法、finalize()、Security Manager——这些在 24/26 上已经从"警告"升级成"报错"或"永久禁用",见第六、七节。
  • 编译目标退化:JDK 22 起--release 8已开始告警(JEP 182 的退役策略),别指望永远靠旧 target 拖着不升级。

三、依赖与字节码的坑

1. 字节码版本

JDK8111721252627
class 文件版本52556165697071

规则:

  • 用目标 JDK 的 javac编译,别用旧 JDK 编新字节码。
  • 第三方库只提供旧字节码一般还能跑(向前兼容),但有些会校验运行 JDK 版本。
  • Maven / Gradle 设release而不是source/target,避免引导类路径错配:
<plugin><artifactId>maven-compiler-plugin</artifactId><configuration><release>25</release><!-- 比 source/target 更安全 --></configuration></plugin>

2. 框架基线(以 Spring 为例,这往往比 JDK 本身更卡人)

版本状态对策
Spring Boot 2.7最后一个支持 Java 8 的一代,OSS 支持已结束必须先跨到 3.x
Spring Boot 3.x基线JDK 17,包名javax.*→jakarta.*先落到 3.5,清完弃用告警
Spring Boot 4.0 / 4.12025-11 / 2026-06 GA,JDK 17 基线 + 一等支持 Java 25;Jakarta EE 11、Jackson 3、移除 JUnit 4 和 Undertow官方推荐路径:3.5 → 清告警 → 4.0

关键顺序:JDK 和框架要一起升,但框架往往先卡住你——Boot 2.7 撑不到 JDK 17+,而 Boot 3+ 又要 JDK 17。所以老项目的第一步通常是「先升框架到 3.x」,再升 JDK。

3. 常见依赖雷区

依赖 / 技术8 → 25/26 的坑对策
JAXB11 移除加jakarta.xml.bind依赖,包名javax→jakarta
Lombok与新 JDK 的 record / 密封类可能有冲突升到支持目标 JDK 的最新版
字节码增强(ASM、CGLIB、javassist、ByteBuddy)高版本 class 文件(70)解析不了升到支持对应 JDK 的版本;新代码可考虑 JDK 自带的 Class-File API(JDK 24 转正)
序列化框架(Kryo、Hessian)模块封装下访问字段受限;部分老版本还用 Unsafe升版本,或用--add-opens止血
早期 Netty / 老版本缓存框架用Unsafe内存访问方法26 上会直接抛异常,必须升到已迁移到 VarHandle / FFM 的版本
APM 探针、Arthas、Mockito inline、Jacoco运行时动态加载 AgentJDK 21 起默认告警,加-XX:+EnableDynamicAgentLoading
日志(Log4j / Logback)基本无碍,注意模块升到近年版本

4. 运行时参数(过渡期)

# 强封装放行(临时)--add-opens java.base/java.lang=ALL-UNNAMED# native 访问放行(JNI / FFM,JDK 24 起)--enable-native-access=ALL-UNNAMED# Agent 动态加载(JDK 21 起)-XX:+EnableDynamicAgentLoading

四、代码层面的改动清单

升到 25 之后,顺手把代码现代化(可选但推荐):

  • var简化局部变量。
  • 文本块替代拼接的多行字符串(SQL / JSON)。
  • List.of/Map.of不可变集合;Optional的isEmpty、ifPresentOrElse。
  • 数据载体换record,有限子类换sealed。
  • switch表达式 + 模式匹配替代if-else instanceof。
  • IO 密集场景试用虚拟线程(newVirtualThreadPerTaskExecutor);链路上下文传递改用ScopedValue(JDK 25 转正)替代ThreadLocal。
  • 25 起可以写import module java.base;,单文件脚本可以省掉类和public static void main。

注意:生产迁移先把"能编译能跑"作为第一目标,现代化重构分批做,别一次梭哈。


五、测试与灰度策略

  1. 编译通过 ≠ 运行正确:务必跑全量单测 + 集成测试。强封装、反射访问、Unsafe 调用都在运行期才暴露。
  2. 启动期验证:关注NoClassDefFoundError、InaccessibleObjectException、UnsupportedOperationException(26 的 Unsafe deny)、UnsupportedClassVersionError。
  3. 性能基线对比:升级后压测,关注 GC 停顿、启动时间、吞吐量。25/26 上可以白拿的收益:-XX:+UseCompactObjectHeaders(官方数据约省 22% 堆、8% CPU)、AOT 缓存(启动提速 20%-40%)。
  4. 灰度发布:新 JDK 先在预发 / 小流量验证,监控异常、GC、线程数;确认稳定再全量。
  5. 可回滚:保留旧 JDK 镜像 / 容器,异常一键回退。

六、JDK 22 → 26:一次跳上去会集中踩到的破坏性变更

从 8 直接往 25/26 跳,这些变更会在一次升级里全部落到你头上,建议提前逐条对照:

版本变更影响与对策
18默认字符集改为 UTF-8(JEP 400)详见下方「中文项目第一坑」
18finalize()标记废弃(JEP 421)改用Cleaner或 try-with-resources
21动态加载 Agent 默认告警(JEP 451)加-XX:+EnableDynamicAgentLoading
23ZGC 默认启用分代模式(JEP 474)显式写-XX:+UseZGC即可,无需额外参数
24移除 ZGC 非分代模式(JEP 490)老的-XX:-ZGenerational直接失效
24Security Manager 永久禁用(JEP 486)见第七节
24JNI 使用开始告警(JEP 472)加--enable-native-access
24sun.misc.Unsafe内存访问默认 warn(JEP 498)见第七节
24移除 Windows 32 位 x86 端口(JEP 479)32 位 Windows 环境必须淘汰
25彻底移除 32 位 x86 端口(JEP 503)同上,Linux 32 位也不再支持
25紧凑对象头转正(JEP 519,非默认)用-XX:+UseCompactObjectHeaders开启
25分代 Shenandoah 转正(JEP 521)低延迟 GC 多一个选择
26sun.misc.Unsafe内存访问默认 deny(JEP 471/498 阶段 3)直接抛异常,见第七节
26深反射修改 final 字段开始告警(JEP 500)见第七节
26移除 Applet API(JEP 504)基本无影响(17 已废弃)
27(已 GA)G1 成为全环境默认 GC(JEP 523)、紧凑对象头默认开启(JEP 534)小内存容器不再自动退回 Serial GC;升 27 时记得重测内存与 GC

中文项目第一坑:默认 UTF-8

JDK 8 / 11 在中文 Windows 上file.encoding默认是 GBK,JDK 18 起默认变成 UTF-8(JEP 400)。老项目里这些写法会突然"能跑但乱码":

byte[]b=str.getBytes();// 不指定字符集 → 8 上是 GBK,18+ 是 UTF-8newString(b);// 同上newFileReader("a.txt");// 用默认字符集,读写都可能错位

另外 JDK 9 起ResourceBundle读取.properties默认按 UTF-8(此前是 ISO-8859-1)。

对策:

  • 全局搜索getBytes()/new String(byte[])/FileReader/FileWriter/InputStreamReader的无字符集重载,显式指定StandardCharsets.UTF_8;
  • 存量文件统一转 UTF-8;
  • 过渡期可显式-Dfile.encoding=UTF-8让 8 和新版本行为一致,先把差异抹平再升级。

回看:9 → 21 还有四个老坑

如果你是从 8 起步,这四条也躲不掉:

  1. 模块强封装(JDK 9 引入,JDK 17 JEP 403 起默认禁止):反射访问 JDK 内部 API 抛InaccessibleObjectException,--illegal-access已失效,只能--add-opens。
  2. JAXB / JAX-WS / CORBA 被移除(JDK 11):补jakarta.*依赖。
  3. 第三方 GC 参数失效:CMS 在 JDK 14 被移除(JEP 363),-XX:+UseConcMarkSweepGC直接启动失败。
  4. _不能当标识符(JDK 9 起是关键字,22 起可作未命名变量)。

七、26 上的五个硬门槛

这一节是本文最值得划线的一段——前四条在 26 上已经不是"警告"而是"可能直接启动失败 / 运行抛异常"。

门槛 1:Security Manager 永久禁用(JDK 24,JEP 486)

  • 现象:System.setSecurityManager(...)抛UnsupportedOperationException;命令行-Djava.security.manager=...除allow外都会报错。
  • 对策:删掉所有 SM 相关代码与启动参数,改用容器 / OS 层安全机制(seccomp、AppArmor、K8s SecurityContext)。依赖 SM 做沙箱的老 RMI / 动态类加载场景必须重新设计。

门槛 2:sun.misc.Unsafe内存访问方法(JDK 26 默认 deny)

五阶段路线:JDK 23 编译期弃用 → 24 默认 warn →26 默认 deny(抛UnsupportedOperationException)→ 之后版本彻底移除。

# 排查谁在用(打印调用栈)java--sun-misc-unsafe-memory-access=debug-jarmyapp.jar# 26 上的临时降级(只能降到 warn,不能再回到 allow)java--sun-misc-unsafe-memory-access=warn-jarmyapp.jar# 也可以用 JFR 精确抓调用点java-XX:StartFlightRecording:filename=rec.jfr-jarmyapp.jar jfr print--eventsjdk.DeprecatedInvocation rec.jfr

真正的解法是升级依赖:绝大多数情况是老版本 Netty、Kryo、Hadoop、Cassandra、某些缓存框架在用。官方替代方案是VarHandle(堆内,JDK 9 起)和FFMMemorySegment(堆外,JDK 22 JEP 454 转正)。

门槛 3:JNI / native 访问限制(JDK 24 起,JEP 472)

  • 现象:加载 native 库或使用 JNI 时告警,未来版本会默认禁止。
  • 对策:--enable-native-access=ALL-UNNAMED(或指定模块名);新代码优先用 FFM API。
  • 注意:Netty、Netty tcnative、RocksDB、各类 JNI 编解码库都可能命中。

门槛 4:深反射修改 final 字段(JDK 26,JEP 500)

  • 现象:26 起,通过Field::setAccessible+set修改final字段会输出告警(每个模块最多一次):
WARNING: Final field f in p.C has been mutated by class com.foo.Bar in module N WARNING: Use --enable-final-field-mutation=N to avoid a warning
  • 逃生:--enable-final-field-mutation=ALL-UNNAMED(注意还要配合--add-opens打开对应包)。
  • 提前演练:--illegal-final-field-mutation=deny,直接让它抛IllegalAccessException,把问题在测试阶段逼出来——未来版本这就是默认行为。
  • 常见命中者:老版本 ORM、序列化 / 反序列化库、mock 框架(Mockito / EasyMock 的某些用法)、自己写的反射 hack。改法是升版本,或改用构造器注入 /VarHandle。

门槛 5:Agent 动态加载告警(JDK 21 起,JEP 451)

  • 现象:运行时 attach agent(APM 探针、Arthas、热部署、Mockito inline、Jacoco on-the-fly)会告警。
  • 对策:-XX:+EnableDynamicAgentLoading放行,长期应改为启动期-javaagent。

八、一套 25 / 26 的启动参数模板

过渡期可以直接照抄,然后逐条把--add-opens/=warn之类的止血项消灭掉:

java\--add-opens java.base/java.lang=ALL-UNNAMED\--enable-native-access=ALL-UNNAMED\--sun-misc-unsafe-memory-access=warn\--enable-final-field-mutation=ALL-UNNAMED\-XX:+EnableDynamicAgentLoading\-XX:+UseZGC\-XX:+UseCompactObjectHeaders\-XX:AOTCache=app.aot\-jarapp.jar

AOT 缓存(Project Leyden)在 25 里是两条命令:-XX:AOTCacheOutput=app.aot训练一次,-XX:AOTCache=app.aot正式运行;26 起支持任意 GC(含 ZGC)。

白拿收益的三条(不用改代码):-XX:+UseCompactObjectHeaders、ZGC / G1 的新版本优化、AOT 缓存。


九、一个典型迁移剧本(8 → 25,26 试点)

1. 跑 jdeps --jdk-internals + jdeprscan --release 25,列风险清单 2. 升级构建工具与核心依赖(Maven/Gradle/Lombok/Netty/Kryo…) 3. 框架先跨过 JDK 17 门槛:Boot 2.7 → 3.5,包名 javax → jakarta 4. 补 JAXB 等被移除模块的依赖 5. 全局搜索无字符集重载(getBytes/new String/FileReader),显式指定 UTF-8 6. 编译目标设为 release 25,本地编译通过 7. 加必要 --add-opens / --enable-native-access 跑单测,修 InaccessibleObjectException 8. 用 --sun-misc-unsafe-memory-access=warn 和 --illegal-final-field-mutation=deny 各跑一轮, 把命中的依赖全部升版本 9. 集成测试 + 压测,对比性能基线(GC 停顿 / 启动时间 / 吞吐) 10. 预发灰度一周,监控异常 / GC / 线程数 11. 全量上线,保留旧版本回滚能力 12. 单独跑一轮 JDK 26 冒烟,验证 HTTP/3、AOT 缓存等新能力;等 JDK 29 LTS 再决定是否跟进 13. (可选)分批做 record/sealed/var/文本块/ScopedValue 现代化

十、本篇记住的清单

  1. 落点选 25 LTS;26 / 27 是非 LTS,只做验证与试点,别当生产终点。下一个 LTS 是 29(预计 2027-09)。
  2. 先用jdeps --jdk-internals+jdeprscan量化风险;26 上再加--sun-misc-unsafe-memory-access=warn和--illegal-final-field-mutation=deny两个照妖镜。
  3. 老项目先升框架(Boot 2.7 是最后支持 Java 8 的一代),再升 JDK。
  4. 用release而非source/target编译;class 文件版本 8=52、17=61、21=65、25=69、26=70。
  5. JDK 18 起默认 UTF-8,中文老项目必须先把无字符集重载改干净。
  6. 26 上的五个硬门槛:Security Manager 永久禁用、Unsafe 内存访问 deny、JNI 限制、final 字段深反射告警、Agent 动态加载告警。
  7. 强封装--add-opens是止血带不是处方,长期必须换掉依赖反射内部的库。
  8. 现代化改造(var / record / sealed / 文本块 / ScopedValue)分批做,先保"能编译能跑"。
  9. 测试全量 + 灰度 + 可回滚,性能基线对比后再全量。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 19:02:27

grpcurl 快速安装:一次打通 gRPC 调用不踩坑

grpcurl 快速安装&#xff1a;一次打通 gRPC 调用不踩坑 【免费下载链接】grpcurl Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers 项目地址: https://gitcode.com/gh_mirrors/gr/grpcurl 拿到一个 gRPC 端点&#xff0c;手上没有 SDK&…

作者头像 李华
网站建设 2026/11/3 18:59:59

企业级落地 :织信如何成为企业级信息化系统的底座

织信如何成为企业级信息化系统的底座企业级信息化落地&#xff0c;不能只看上线了几个系统。真正难的地方&#xff0c;是这些系统能不能在同一个底座上运行。生产、设备、质量、仓库、供应链、客户、项目、办公&#xff0c;如果每个系统都是单独建设&#xff0c;短期看都能解决…

作者头像 李华
网站建设 2026/11/3 18:42:54

如何用8GB显卡玩转4K视频生成:阿里WanVideo_comfy完整指南

如何用8GB显卡玩转4K视频生成&#xff1a;阿里WanVideo_comfy完整指南 在AI视频生成领域&#xff0c;专业工具普遍存在"高门槛、高成本、高复杂度"的三高问题&#xff0c;让普通创作者望而却步。阿里巴巴通义万相团队开源的WanVideo_comfy项目通过ComfyUI生态实现电…

作者头像 李华