小易说IT 小易说IT

JDK 跨 LTS 版本升级:注意事项与常见踩坑点清单

整体原则:优先相邻 LTS 递进升级,避免跨多个大版本跳级;先编译通过、再功能验证、最后性能调优。以下按升级路径分类梳理核心问题、现象与解决方案。

一、通用升级前置准备(所有版本升级必做)

1. 依赖与组件兼容性排查

  • 全量梳理项目依赖:通过 mvn dependency:tree 或 Gradle 依赖分析,确认核心框架、中间件客户端、工具类库的 JDK 兼容版本。

  • 重点校验高敏感依赖:Lombok、ASM/CGLIB/Javassist、Netty、Guava、Jackson、Dubbo、Hibernate/MyBatis 等字节码增强、反射类库。

  • 避免极端老版本:停止维护的第三方组件(如 commons-lang 2.x、老版本 Dubbo 2.6 及以下)大概率不兼容高版本 JDK,需提前替换或升级。

2. 构建工具与编译链升级

  • Maven:JDK 11 需 3.6.3+,JDK 17 需 3.8.6+,JDK 21/25 需 3.9.6+;maven-compiler-plugin 建议升级到 3.11.0+,否则无法识别高版本源码格式。

  • Gradle:JDK 17 需 7.3+,JDK 21 需 8.5+,JDK 25 需 8.10+。

  • 编译插件同步升级:Lombok、MapStruct、Spring Boot Maven Plugin 等必须升级到对应兼容版本。

3. JVM 启动参数预清理

  • 先移除所有 GC 调优、实验性参数,使用 JDK 默认配置跑通业务,再逐步针对性调优。

  • 提前排查已废弃 / 移除的参数(下文详细列出),避免启动直接报错。

4. 测试体系前置校验

  • 覆盖单元测试、接口集成测试、全链路压测,重点验证反射、动态代理、序列化、文件 IO、HTTPS 调用场景。

  • 确认监控探针(Arthas、SkyWalking、Pinpoint)、日志框架、链路追踪组件兼容目标 JDK 版本。

二、分升级路径核心踩坑点

1. JDK 8 → JDK 11(最高频升级路径,坑点最多)

这是存量系统最主流的升级路径,核心变化是模块化系统落地、Java EE 模块移除。

表格

踩坑场景

现象与原因

解决方案

Java EE/CORBA 类缺失

启动报 ClassNotFoundException,常见类:javax.xml.bind.JAXBContextjavax.activation.DataHandlerjavax.annotation.PostConstructjavax.transaction.UserTransaction。原因:JDK 11 正式移除 Java EE 与 CORBA 相关模块。

引入独立第三方依赖,例如:

- JAXB:jaxb-api + jaxb-impl + jaxb-core

- 注解:javax.annotation-api

- 激活框架:javax.activation-api

内部 API 反射访问警告

启动出现大量 WARNING: Illegal reflective access 日志。原因:JDK 9 开始限制对 sun.misc.* 等内部 API 的反射访问,JDK 8 无限制。

1. 优先升级触发警告的第三方库(如 Netty、Guava)到兼容版本;

2. 临时兜底:加启动参数 --illegal-access=permit(仅 JDK 9-16 有效,17 后失效)

永久代参数启动报错

启动报 Unrecognized VM option 'MaxPermSize'。原因:JDK 8 已用元空间替代永久代,JDK 9 后彻底移除永久代参数。

删除 -XX:PermSize-XX:MaxPermSize,替换为元空间参数:-XX:MetaspaceSize-XX:MaxMetaspaceSize

Lombok / 字节码库编译失败

编译时报错、类无法生成,或运行报 ClassFormatError。原因:低版本 Lombok、ASM、CGLIB 不支持高版本字节码格式。

升级对应依赖:Lombok ≥ 1.18.10,ASM ≥ 7.0,CGLIB ≥ 3.3.0

弱加密算法连接失败

HTTPS、数据库 SSL 连接握手失败。原因:JDK 11 默认禁用 SSLv3、RC4、3DES 等弱加密算法,收紧证书校验规则。

1. 优先升级服务端加密套件;

2. 特殊场景可临时修改 java.security 配置放开限制(不推荐)

Nashorn JS 引擎废弃

脚本执行出现废弃警告。原因:JDK 11 标记 Nashorn 为废弃,JDK 15 正式移除。

提前替换为 GraalJS、Rhino 等独立 JS 引擎

2. JDK 11 → JDK 17(当前企业主流升级,强封装是核心门槛)

核心变化:内部 API 强封装默认生效、大量旧 API 正式移除、安全基线收紧。

表格

踩坑场景

现象与原因

解决方案

反射访问直接报错

运行报 IllegalAccessError / IllegalAccessException,提示模块未导出。原因:JDK 17 彻底移除 --illegal-access 参数,默认禁止所有非法反射访问。

1. 首选方案:升级触发问题的第三方库到支持 JDK 17 的版本(如 Netty ≥ 4.1.68、Guava ≥ 31.0-jre);

2. 临时兜底:通过启动参数手动开放模块,例如:

--add-opens java.base/java.lang=ALL-UNNAMED

--add-opens java.base/java.util=ALL-UNNAMED

CMS GC 参数启动失败

启动报 Unrecognized VM option 'UseConcMarkSweepGC'。原因:JDK 14 已正式移除 CMS 垃圾回收器。

替换为 G1(默认)或 ZGC:-XX:+UseG1GC-XX:+UseZGC

RMI / 旧 API 直接缺失

启动报 NoClassDefFoundError。原因:JDK 17 正式移除 RMI 激活机制、Applet API 等遗留组件。

替换对应实现,移除对废弃 API 的依赖;Applet 相关代码直接删除

SecurityManager 警告

启动出现安全管理器废弃警告。原因:JDK 17 正式标记 SecurityManager 为废弃,后续版本将移除。

逐步替换为模块系统、权限控制框架等替代方案

Spring 生态版本不兼容

启动失败、注解不生效。原因:Spring Boot 2.7 及以下对 JDK 17 兼容有限,Spring Boot 3.x 才将 JDK 17 作为基线。

1. Spring Boot 2.x 升级到 2.7.x 最新版,可兼容运行;

2. 长期规划建议同步升级到 Spring Boot 3.x

3. JDK 17 → JDK 21(并发模型升级,虚拟线程易踩坑)

整体兼容性较好,核心风险集中在虚拟线程误用、默认字符集变更。

表格

踩坑场景

现象与原因

解决方案

默认字符集变更导致乱码

中文环境下文件读写、字符串转码出现乱码。原因:JDK 18 开始默认字符集统一为 UTF-8,不再跟随操作系统(Windows 旧版默认 GBK)。

1. 代码中所有 IO、字符串转码显式指定 StandardCharsets.UTF_8

2. 兼容老系统可临时加启动参数 -Dfile.encoding=GBK(不推荐长期使用)

虚拟线程池化反而性能下降

改用虚拟线程后并发能力无提升,甚至出现线程阻塞、OOM。原因:虚拟线程是轻量级的,池化会浪费能力;synchronized 阻塞、Native 方法调用会导致线程 Pinning(固定在平台线程上无法释放)。

1. 虚拟线程不要池化,每次任务直接新建,使用 Executors.newVirtualThreadPerTaskExecutor()

2. 替换高频阻塞场景的 synchronizedReentrantLock,减少 Pinning;

3. 先在 IO 密集型场景落地,CPU 密集型收益有限

ThreadLocal/MDC 上下文异常

日志 MDC 上下文丢失、内存占用异常升高。原因:虚拟线程数量可达百万级,ThreadLocal 会大幅增加内存开销;部分库的线程上下文未适配虚拟线程。

1. 逐步替换为 Scoped Values(JDK 25 正式发布);

2. 确保日志框架、MDC 升级到适配虚拟线程的版本;

3. 避免在虚拟线程中存储大对象到 ThreadLocal

自定义集合类编译报错

实现 List/Map 的自定义类出现方法冲突。原因:JDK 21 新增 SequencedCollection/SequencedMap 接口,List、Map 继承后新增默认方法。

检查自定义集合中的同名方法,调整实现逻辑,避免与默认方法冲突

finalize () 废弃警告

重写 finalize() 的类出现大量警告。原因:JDK 21 进一步标记 finalize() 为待移除,未来版本会直接删除。

替换为 try-with-resourcesCleaner 机制实现资源释放

4. JDK 21 → JDK 25(新特性迭代,整体兼容性较好)

作为最新 LTS,核心是性能优化与安全升级,业务代码层面坑点较少,主要关注底层依赖与移除 API。

表格

踩坑场景

现象与原因

解决方案

Applet API 正式移除

遗留代码编译 / 运行报错。原因:JDK 25 正式移除 Applet API 相关类。

删除相关遗留代码,无替代场景

非分代 ZGC 参数失效

启动参数报错。原因:JDK 24 起正式移除非分代 ZGC 模式,默认启用分代 ZGC。

移除非分代相关的 ZGC 调优参数,直接使用默认分代模式

预览版 API 不兼容

之前使用的预览版 API 编译失败。原因:Scoped Values、结构化并发等从预览转正,API 签名有调整。

按照正式版 API 规范修改业务代码

后量子密码算法影响 SSL

旧版 SSL 握手异常。原因:新增 ML-KEM/ML-DSA 等后量子算法,默认加密套件优先级调整。

确认服务端加密套件兼容性,必要时手动指定启用的加密算法

底层内存操作库异常

基于 Unsafe、对象头操作的底层库运行异常。原因:紧凑对象头正式落地,对象内存布局发生变化。

升级对应底层库到支持 JDK 25 的版本;业务代码不建议直接依赖对象内存布局

三、全版本通用高频踩坑点

1. 字节码增强类依赖不兼容

  • 典型表现:编译失败、类加载报错、代理不生效。

  • 高风险库:Lombok、MapStruct、CGLIB、ASM、Javassist、Byte Buddy。

  • 避坑原则:升级 JDK 前,先把这些库升到最新稳定版,它们对 JDK 版本的敏感度远高于普通业务代码。

2. JVM 参数高频失效清单

表格

旧参数

状态

替代方案

-XX:PermSize / -XX:MaxPermSize

JDK 9+ 移除

-XX:MetaspaceSize / -XX:MaxMetaspaceSize

-XX:+UseConcMarkSweepGC

JDK 14+ 移除

-XX:+UseG1GC / -XX:+UseZGC

-XX:+UseParNewGC

JDK 9+ 废弃、后续移除

配合 G1/ZGC 使用,无需单独配置

--illegal-access=permit

JDK 17+ 移除

--add-opens / --add-exports 精准开放

-XX:+UseSerialGC

仍可用,但不推荐服务端使用

生产环境优先 G1 或 ZGC

3. 监控与运维探针适配

  • Arthas、SkyWalking、Pinpoint、Prometheus 等探针版本过低,会导致应用启动失败、监控数据丢失、性能损耗飙升。

  • 升级前务必确认探针版本支持目标 JDK,优先在测试环境验证探针注入效果。

4. 序列化兼容性问题

  • 跨大版本升级后,JDK 内置类的序列化 ID 可能变化,导致历史序列化数据反序列化失败。

  • 依赖 JDK 原生序列化的缓存、RPC 场景,升级前需做兼容性验证;建议长期替换为 JSON、Protobuf 等跨版本兼容的序列化方案。

四、升级避坑最佳实践

  1. 递进式升级:不建议从 JDK 8 直接跳到 JDK 21/25,优先按 8→11→17→21 的顺序逐步升级,每步验证问题、逐步修复。

  2. 编译期问题优先解决:先保证代码编译通过、所有单元测试跑通,再部署到测试环境做集成验证。

  3. 参数最小化原则:升级初期只保留必要启动参数,跑通业务后再逐步调优 GC、内存配置,避免一开始就被参数问题阻塞。

  4. 灰度发布验证:生产环境先灰度少量节点,观察错误日志、GC 指标、接口响应耗时,确认无异常后全量发布。

  5. 废弃警告前置处理:重视启动日志中的 WARNING 废弃提示,提前替换,避免下一次升级时直接变成报错。


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/jdk-lts-version-upgrade-precautions-pitfalls-guide

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/