整体原则:优先相邻 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 启动参数预清理
4. 测试体系前置校验
二、分升级路径核心踩坑点
1. JDK 8 → JDK 11(最高频升级路径,坑点最多)
这是存量系统最主流的升级路径,核心变化是模块化系统落地、Java EE 模块移除。
表格
踩坑场景 | 现象与原因 | 解决方案 |
|---|
Java EE/CORBA 类缺失 | 启动报 ClassNotFoundException,常见类:javax.xml.bind.JAXBContext、javax.activation.DataHandler、javax.annotation.PostConstruct、javax.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. 替换高频阻塞场景的 synchronized 为 ReentrantLock,减少 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-resources 或 Cleaner 机制实现资源释放 |
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. 字节码增强类依赖不兼容
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. 监控与运维探针适配
4. 序列化兼容性问题
四、升级避坑最佳实践
递进式升级:不建议从 JDK 8 直接跳到 JDK 21/25,优先按 8→11→17→21 的顺序逐步升级,每步验证问题、逐步修复。
编译期问题优先解决:先保证代码编译通过、所有单元测试跑通,再部署到测试环境做集成验证。
参数最小化原则:升级初期只保留必要启动参数,跑通业务后再逐步调优 GC、内存配置,避免一开始就被参数问题阻塞。
灰度发布验证:生产环境先灰度少量节点,观察错误日志、GC 指标、接口响应耗时,确认无异常后全量发布。
废弃警告前置处理:重视启动日志中的 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/