
升级 JDK 就像给汽车换发动机——看着简单拆开全是线。这份清单帮你提前排雷。一、升级前的通用准备所有版本通用无论你从哪个版本开始升级以下准备工作是必修课1.1 依赖梳理bash# 导出完整的依赖树排查第三方库兼容性 mvn dependency:tree -Dverbose deps.txt重点关注以下依赖的版本是否支持目标 JDK依赖JDK 17 最低版本JDK 21 最低版本JDK 25 最低版本Maven3.6.33.8.83.9.6Gradle7.38.08.5Spring Boot2.7.x / 3.x3.13.4 / 4.0Spring Cloud2021.0.x2022.0.x2024.0.xMyBatis-Plus3.5.3.13.5.53.5.7MySQL Connector8.0.258.0.338.0.35 / 9.xNetty4.1.684.1.944.1.100Hibernate5.6 / 6.x6.26.41.2 代码扫描# 检测对 JDK 内部 API 的依赖必做 jdeps --jdk-internals -R your-app.jar # 检测废弃 API jdeprscan your-app.jar1.3 通用 JVM 参数调整旧参数JDK 8/11新参数JDK 17说明-XX:PrintGCDetails-Xlog:gc*日志系统统一-XX:PrintGCTimeStamps-Xlog:gc*同上-XX:PrintGCDateStamps-Xlog:gc*同上-XX:UseParallelGC-XX:UseG1GC或-XX:UseZGC默认 GC 已变-XX:MaxMetaspaceSize保留可用元空间管理优化-XX:UseContainerSupportJDK 10 默认开启容器感知--illegal-accesspermitJDK 17 已删除必须用--add-opens替代1.4 灰度发布规范阶段操作验证指标阶段 1本地开发环境改造编译通过、单元测试通过阶段 2测试环境全链路压测P99 延迟、GC 停顿、内存无泄漏阶段 3线上灰度 10% 流量稳定运行 24~72 小时阶段 4逐步放量至 100%监控无告警回滚条件满足任意一条立即回滚P99 接口延迟上涨超过 50%GC STW 持续超过 200ms出现反射 / 模块访问异常容器被 OOM Kill 频率上升二、JDK 7 → JDK 8 迁移难度⭐⭐ |推荐路径JDK 7 → JDK 8直接升级⚠️ 主要变更变更项说明解决方案PermGen 被 Metaspace 替代-XX:PermSize、-XX:MaxPermSize失效替换为-XX:MetaspaceSize、-XX:MaxMetaspaceSizeNashorn JS 引擎替代 Rhinojavax.script实现变更代码无需改动但注意 JS 兼容性日期 API 迁移Date/Calendar仍可用但推荐替换新代码直接用java.time✅ 推荐操作移除 JVM 参数中的-XX:PermSize和-XX:MaxPermSize新代码全面使用java.time替代Date/Calendar尝试在代码中引入 Lambda 和 Stream 简化逻辑非强制三、JDK 8 → JDK 11 迁移难度⭐⭐⭐ |推荐路径JDK 8 → JDK 11建议先到 11 再过渡到更高版本⚠️ 主要变更变更项说明解决方案模块化系统 JPMS 引入JDK 内部 API 不再默认开放添加--add-opens启动参数javax.xml.bind(JAXB) 移除不再随 JDK 提供手动添加 Maven 依赖javax.xml.ws(JAX-WS) 移除同上手动添加 Maven 依赖javax.corba移除CORBA 相关 API 完全删除迁移或移除相关功能sun.misc.BASE64Encoder移除编译报错替换为java.util.Base64sun.misc.Unsafe受限部分操作受限使用VarHandle替代JavaFX 分离不再随 JDK 分发单独引入 OpenJFX 模块默认 GC 改为 G1JDK 8 默认是 Parallel GC评估 GC 行为变化调整参数 常用--add-opens参数# 如果遇到反射访问受限添加以下参数按需组合 --add-opens java.base/java.langALL-UNNAMED --add-opens java.base/java.lang.reflectALL-UNNAMED --add-opens java.base/java.utilALL-UNNAMED --add-opens java.base/java.ioALL-UNNAMED --add-opens java.base/sun.security.providerALL-UNNAMED 缺失依赖添加!-- JAXB -- dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency dependency groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId version2.3.1/version /dependency !-- JAX-WS -- dependency groupIdjavax.xml.ws/groupId artifactIdjaxws-api/artifactId version2.3.1/version /dependency✏️ 代码修改示例// ❌ JDK 8 写法JDK 11 编译报错 import sun.misc.BASE64Encoder; String encoded new BASE64Encoder().encode(Hello.getBytes()); // ✅ JDK 11 写法 import java.util.Base64; String encoded Base64.getEncoder().encodeToString(Hello.getBytes());四、JDK 8 → JDK 17 迁移重点跨度最大难度⭐⭐⭐⭐ |推荐路径JDK 8 → JDK 17推荐直接升级跳过 JDK 11⚠️ JDK 8 到 17 的核心雷区类别具体问题解决方案API 移除sun.misc.*全部不可访问替换为标准 API模块系统JPMS 强封装反射受限添加--add-opens或升级框架反射限制setAccessible(true)部分失效使用MethodHandles或添加模块权限GC 变化默认 G1ZGC 可用重新评估 GC 参数配置容器适配旧版本无容器感知开启-XX:UseContainerSupport安全管理器SecurityManager废弃迁移安全策略Applet API已删除移除相关依赖AOT/JIT 编译器实验性 Graal JIT 移除改用 GraalVM 独立方案-XX:AggressiveOpts已删除移除该参数 Maven 配置更新properties java.version17/java.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target maven.compiler.release17/maven.compiler.release /properties build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.10.1/version configuration source17/source target17/target compilerArgs arg--release/arg arg17/arg /compilerArgs /configuration /plugin /plugins /build✏️ 常见代码迁移对照表JDK 8 写法JDK 17 写法说明new BASE64Encoder().encode(bytes)Base64.getEncoder().encodeToString(bytes)BASE64 编码sun.misc.Unsafe.allocateInstance()Constructor.newInstance()或VarHandle对象实例化if (obj instanceof String) { String s (String) obj; }if (obj instanceof String s) { ... }instanceof 模式匹配手写 POJO getter/setterpublic record User(...) {}Record 替代String json {\n \name\: \a\\n}String json { name: a } 文本块switch(x) { case 1: break; ... }switch(x) { case 1 - ... }Switch 表达式五、JDK 11 → JDK 17 迁移难度⭐⭐ |推荐路径JDK 11 → JDK 17相对平滑⚠️ 主要变更变更项说明解决方案强封装内部 API--illegal-accesspermit不再可用必须用--add-opens显式开放SecurityManager 废弃标记为废弃待移除迁移安全策略finalize()废弃标记为废弃改用Cleaner或PhantomReferenceRMI Activation 移除已删除移除相关依赖Applet API 废弃已废弃移除相关依赖GC 选项ZGC/Shenandoah 转正可选启用✅ 核心操作移除 JVM 参数中的--illegal-accesspermit检查反射代码替换为--add-opens或MethodHandles将finalize()方法迁移到Cleaner机制考虑启用 ZGC低延迟场景或 ShenandoahARM 场景六、JDK 17 → JDK 21 迁移难度⭐⭐ |推荐路径JDK 17 → JDK 21升级体验最好的跳段⚠️ 主要变更变更项说明解决方案虚拟线程正式转正Thread.ofVirtual()可用可引入但无需强制修改现有代码String Templates预览STR.Hello \{name}预览特性生产慎用finalize()正式废弃待移除编译警告迁移到Cleaner分代 ZGC 可用默认不分代需手动开启-XX:UseZGC -XX:ZGenerationalsynchronized Pinning 问题虚拟线程中使用synchronized会被钉死建议用ReentrantLock替代JDK 25 已修复⚠️ 虚拟线程使用注意事项// ✅ 正确用法IO 密集型场景 try (var executor Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() - handleIoRequest()); } // ❌ 避免不要在虚拟线程中使用 synchronized 块JDK 21 版本 synchronized (lock) { // 会导致 Pinning并发能力暴跌 // ... } // ✅ 改用 ReentrantLock lock.lock(); try { // ... } finally { lock.unlock(); } // ❌ 避免不要池化虚拟线程虚拟线程不是池化资源 // ❌ Executors.newFixedThreadPool(100) 不对 // ✅ 每次任务新建虚拟线程 GC 配置建议# 低延迟场景推荐 -XX:UseZGC -XX:ZGenerational -Xms4g -Xmx4g # 通用场景 -XX:UseG1GC -XX:MaxGCPauseMillis200七、JDK 21 → JDK 25 迁移难度⭐ |推荐路径JDK 21 → JDK 25最顺滑的升级⚠️ 主要变更变更项说明解决方案ScopedValues 正式转正替代 ThreadLocal新代码优先使用synchronized Pinning 修复虚拟线程中可安全使用 synchronized代码无需修改紧凑对象头默认开启对象头 16B→8B省内存无需操作默认生效分代 ZGC 默认开启吞吐量大幅提升无需操作分代 Shenandoah 转正新增分代模式按需启用32 位 x86 支持移除不再支持 32 位系统确保使用 64 位系统紧凑源文件 实例 mainvoid main() {}替代传统写法新代码可用灵活构造函数体super()前可写校验逻辑新代码可用后量子加密 KDF API新增密钥派生函数 API按需使用✅ 核心操作几乎无需修改现有代码——JDK 21 到 25 的兼容性极好之前为了规避 Pinning 而改写的ReentrantLock可以改回synchronized但不强制评估是否将ThreadLocal替换为ScopedValues新模块推荐存量模块可选移除 JVM 参数中手动开启分代 ZGC 的参数JDK 25 默认已开启享受紧凑对象头带来的内存红利约 15%~22% 的内存节省✏️ 新特性示例代码// ScopedValues 替代 ThreadLocal private static final ScopedValueString TRACE_ID ScopedValue.newInstance(); void handleRequest() { ScopedValue.where(TRACE_ID, trace-123).run(() - { // 整个作用域内都能获取到 TRACE_ID String id TRACE_ID.get(); process(id); }); // 自动释放绝不泄漏 } // 实例 main 方法 // Hello.java void main() { System.out.println(Hello, JDK 25!); } // 灵活构造函数体 class User { User(String name) { if (name null || name.isBlank()) { throw new IllegalArgumentException(名称不能为空); } // ✅ 在 super() 前做校验 super(); } }八、一键速查各升级路径对比升级路径难度主要工作量推荐度建议策略JDK 7 → 8⭐⭐低主要是 PermGen 参数调整✅直接升7 太老了JDK 8 → 11⭐⭐⭐中需处理模块化、移除 API✅可作为过渡JDK 8 → 17⭐⭐⭐⭐高需一次性处理模块反射GC依赖✅✅✅推荐直接升 17JDK 11 → 17⭐⭐低改动量小✅✅推荐JDK 11 → 21⭐⭐⭐中可享受虚拟线程✅✅推荐JDK 17 → 21⭐⭐低注意 synchronized Pinning✅✅✅强烈推荐JDK 17 → 25⭐⭐低改动量极小✅✅✅强烈推荐一步到位JDK 21 → 25⭐极低几乎无痛升级✅✅✅✅最顺滑的升级九、最后的一些血泪教训不要直接在生产环境换 JDK 版本——先走灰度哪怕你觉得应该没问题工具链版本要一起升——Maven、Gradle、IDEA 都得跟上否则编译报错会让你怀疑人生--add-opens是过渡方案不是长期方案——依赖它的第三方库尽快升级到兼容版本GC 参数不是玄学——换了 JDK 版本一定要做 GC 压测同样的参数在不同 JDK 上表现可能完全不同虚拟线程不是万能药——它适合 IO 密集型不适合 CPU 密集型32 位已死有事烧纸——JDK 25 彻底移除了 32 位 x86 支持还在用 32 位系统的赶紧升级一句话总结JDK 8 → 17 跨度最大但最值得JDK 17 → 21 → 25 升级一次比一次顺滑。早升早享受晚升……也不是不行但安全补丁可不等人。