AICon 北京站 Keynote 亮点揭秘,想了解 Agent 智能体来就对了! 了解详情
写点什么

企业级 JDK 升级实战:660 个项目从 JDK8 到 JDK21 的零故障升级之路

朴朴科技平台组 InfoQ

  • 2025-06-23
    北京
  • 本文字数:10072 字

    阅读完需:约 33 分钟

大小:3.30M时长:19:14
企业级 JDK 升级实战:660 个项目从 JDK8 到 JDK21 的零故障升级之路

作者 | 朴朴科技平台组


前  言


在企业级系统架构演进中,是否进行 JDK 版本升级往往是一个令人头疼的难题。一方面,升级可以享受新版本带来的性能提升和特性增强,另一方面,升级需要面对潜在的兼容性风险和巨大的升级成本。本文将分享我们如何在没有生产故障的前提下,用 6 个月时间,完成 660 个项目从 JDK8 升级到 JDK21 的完整实践,希望能为读者提供参考和借鉴。


背景与动机


现状困境


多年来,我们一直以 JDK8 作为后端 Java 研发的主力版本。然而,随着业务量的持续增长与行业技术的持续演进, JDK8 逐渐暴露出以下问题:


性能与资源瓶颈


随着业务量的增长,JVM 的内存占用和 CPU 使用率不断攀升,部分核心服务需不断扩容计算资源来维持业务正常运转,但这也造成了一定的资源浪费,并且运维的压力日趋加价。


生态兼容受限


Java 社区已将高版本 JDK 作为新技术演进的主战场,且众多新一代主流开源项目(如 Spring Boot 3.x、Kafka 4.0 等)已陆续停止 JDK8 支持,这使得相关依赖的升级变得繁琐,组件兼容性风险逐步显现。


技术持续演化受阻


JDK8 缺乏后期版本 Java 提供的语言特性、开发工具与监控能力,团队难以拥抱新特性和新模式,影响开发体验与效率,阻碍了技术持续演进。


安全可控性下降


JDK8 的历史稳定并不代表未来依然安全。随着攻击方式和行业合规要求的升级,老版本系统面临的新型威胁和治理难度将不断上升,维持旧版本将带来更高的安全和运维压力。


为应对上述问题和挑战,我们启动了 JDK 版本升级专项,力求从根本上解决瓶颈问题,消除历史技术包袱,增强企业的技术创新力。


升级价值


在升级之前,我们系统地评估了将 JDK8 升级到 JDK21 可以带来的价值,具体主要体现在以下几个方面:


性能提升


JDK21 对 JIT 编译器、线程并发管理、垃圾回收、对象管理、内存分配等几个方面进行了优化。比如,下面两图展示的就是同样使用 G1 GC 的情况下,JDK21 相比 JDK8 吞吐量提升近 50%,内存使用率下降了近 60%。




新的语言特性和功能支持


  • 引入了 Record、Pattern Matching、Switch Expression 等现代化 Java 语言特性,有效提升了代码可读性与开发效率;

  • 支持虚拟线程(Virtual Threads),极大简化高并发场景下的线程管理,实现更轻量级的并发编程;

  • 提供更丰富的应用诊断与监控工具,为线上问题定位和自动化运维提供了坚实基础。


生态与未来演进能力


  • 与业界主流开源项目和技术框架保持同步,确保能够顺利对接行业最佳实践,避免演进受阻;

  • 持续获得社区安全补丁更新、新功能以及最佳实践支持。


风险与挑战


升级的价值令人期待,但大规模基础架构升级绝非一帆风顺。为了确保整个工程能够顺利推进,我们不仅前置梳理了 JDK 升级带来的潜在风险,也提前对实际落地过程中的可能存在挑战进行了分析。


核心风险识别


JDK 升级涉及的风险不仅在技术兼容层面,更广泛覆盖业务连续性、工程配合和运维响应等多个领域。我们将风险主要归类为三种:


兼容性风险:


  • 模块化与反射限制:JDK9 引入模块化系统,对反射访问非公开类和成员施加了更多限制,可能导致运行时反射场景异常;

  • 依赖包兼容性:部分二方包、三方库可能尚未适配高版本 JDK,存在 API 调用、字节码生成等兼容性问题;

  • API 废弃与移除:JDK8 中的部分接口与类在新版本被标记为废弃或彻底移除,程序依赖这些 API 将无法编译或运行;

  • 标准库实现行为变化:JDK 方法或类中存在方法名相同,但其行为或返回结果与旧版本不完全一致的场景,可能引发业务逻辑偏差;

  • 测试工具链与单元测试框架兼容性:如 JUnit、Mockito、PowerMock 等主流测试框架及配套插件,部分历史版本不支持高版本 JDK,升级后可能导致单测无法运行或 Mock 失效,需同步升级配套版本;

  • 构建与运维脚本兼容性:包括 Maven、Gradle 等构建工具、CI/CD 流程中的脚本,若未及时适配高版本 JDK,容易在打包、发布过程中出错。


运维风险:


  • 手工操作风险:升级过程涉及的配置改动较多,依靠人工易出错,升级风险如何把控;

  • 环境配置一致性:升级后各环节中的参数、JVM 配置是否同步,不一致会引发环境相关问题;


隐藏风险:


  • 业务功能一致性:JDK 版本升级涉及较多的依赖包版本升级,如何确保各服务升级前后的业务功能表现一致;

  • 隐藏 bug:升级后可能会引入难以发现或定位的 Bug;


升级挑战


识别风险只是起点,对于如此大规模系统迁移,在项目实施阶段还面临一系列工程和组织层面的实际挑战。我们梳理了下在本次 JDK 版本升级过程中,挑战主要来自以下几方面:


依赖包量大且关系复杂


项目中存在庞大的自研二方包、三方依赖库,以及各式各样的插件。依赖之间彼此交错,不同应用采用的版本差异也很大,部分底层依赖包可能社区都已经停止维护好多年了,这使得兼容性验证和适配工作量巨大。


项目体量庞大


全公司需升级的核心应用超过 660 个,涵盖了订单、库存、支付、数据分析等各类关键业务模块。庞大的项目数量极大增加了版本兼容性改造、功能回归测试和上线推进的难度。


跨多团队协助


升级项目分布在数十个业务团队和基础架构、运维、测试、稳定等多个职能组。需统筹协调各方资源、确保信息同步、统一进度调控,对组织横向协同和流程治理能力是一个巨大挑战。


升级目标


经过以上分析,团队清晰地认识整个升级专项的难度与挑战。为了能有效地把控升级节奏、降低潜在风险,并确保最终达到预期收益,针对本次专项我们制定了以下核心目标:


按期完成


在 6 个月内完成包括前期调研、工具建设以及 660 多个项目从 JDK8 至 JDK21 升级等工作。


无 P3 以上故障


升级过程中确保业务稳定不受影响,因升级引发的业务故障等级最高不超过 P3,且数量不超过 1。


高效低成本交付


单项目升级总时长控制在 1 小时以内,过程自动化、一键操作为主,最大程度降低人力和协作投入。


升级过程开发无感


升级由平台团队牵头,业务开发团队仅需最小配合,确保业务研发、交付进度和用户体验不受影响。


升级流程与落地实践


整体升级策略


为了确保以上升级目标能够实际落地,我们制定了“风险前置、工具先行、分批推进”的总体升级策略。核心理念是在前置环节充分识别和消解升级难点,再通过自动化和规范化工具,最大限度降低团队协作和技术操作门槛,从而保障 2 个季度内按计划、高质量实现业务无感知升级。此外,为了应对预期外的异常,升级方案支持自动回退机制,保障风险可控和业务连续。



上图是围绕这一策略制定的整个升级流程的关键时间节点。在整个升级流程中,最值得关注的是“兼容性问题的全量识别与处理”、“升级工具优化”以及“分级分批推进”这 3 部分,以下将依次展开介绍。


兼容性扫描与方案制定


兼容性深度扫描


通过前置步骤的梳理我们发现扫描的目标对象量大且依赖之间彼此交错,纯靠人工是无法完成的,因此我们引进了开源的 EMT4J 扫描工具。依靠工具和人工验证相结合的方式,对以下关键对象实行全面扫描:


  • 服务依赖库


通过 EMT4J 工具对公司各服务所用到的所有插件、二方包和三方包依赖进行兼容性扫描,共计扫描了 2800+ 个依赖包。扫描完后发现总共有 130 个二方、三方包存在兼容性问题。


  • 测试与质量平台


检视核心单元测试框架(如 JUnit、Mockito 等)、Sonar 等工具链在新 JDK 下是否可用、Mock 能力是否失效。验证发现单测框架和 sonar 都存在兼容性问题,需要升级适配。


  • CICD 相关


梳理 Maven 构建脚本、CICD 流水线、调试工具(arthas)等对 JDK21 的兼容能力。通用验证发现除了构建部署脚本存在兼容性问题需要改造外,arthas 的部分功能在 JDK21 下也无法使用,需要升级适配。


  • 监控与告警体系


检查应用在新 JDK 版本下的指标采集、链路追踪、告警平台等是否正常,确定升级 JDK 版本对监控告警的影响。验证过程中未发现监控和告警存在不兼容问题。


问题归类与解决方案


对于扫描发现的问题,我们统一收集汇总,并逐个给出解决方案。总的来看,JDK8 到 JDK21 升级产生的兼容性问题可以归纳为 3 大类:反射访问限制、依赖包兼容性,以及参数配置项变化。各类问题的产生原因和解决方案归纳汇总如下:


反射访问问题:


  • 问题原因:JDK9 引入模块化,限制对非公开类或成员的反射访问,原先用反射访问的场景,在升级后会直接触发 InaccessibleObjectException,下图是反射访问 java.lang 的报错信息。


JavaCaused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @7a5ceedd        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)        at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)        at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)        at com.google.inject.internal.cglib.core.$ReflectUtils$1.run(ReflectUtils.java:52)        at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)        at com.google.inject.internal.cglib.core.$ReflectUtils.<clinit>(ReflectUtils.java:42)
复制代码


  • 解决方案:这类问题可统一通过模块开放参数 --add-opens 来解决。但这里需要注意, 每条 --add-opens 配置只开放了这个指定模块下的指定包本身,不会递归开放其子包,比如 --add-opens java.base/java.lang=ALL-UNNAMED 只开放 java.lang 包,并不会包含 java.lang.annotation 等子包,所以每个要被反射访问的包都需要独立开放,以下是目前我们完整的开放包列表(出于篇幅考虑,对于多个子包开放的情况用省略号替代)。


SQL--add-reads java.base=ALL-UNNAMED--add-reads java.management=ALL-UNNAMED--add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED--add-opens java.base/java.io=ALL-UNNAMED--add-opens java.base/java.lang=ALL-UNNAMED......--add-opens java.base/java.math=ALL-UNNAMED--add-opens java.base/java.net=ALL-UNNAMED--add-opens java.base/java.net.spi=ALL-UNNAMED--add-opens java.base/java.nio=ALL-UNNAMED......--add-opens java.base/java.security=ALL-UNNAMED......--add-opens java.base/java.text=ALL-UNNAMED--add-opens java.base/java.text.spi=ALL-UNNAMED--add-opens java.base/java.time=ALL-UNNAMED......--add-opens java.base/java.util=ALL-UNNAMED......--add-opens java.base/javax.crypto=ALL-UNNAMED......--add-opens java.base/javax.net=ALL-UNNAMED--add-opens java.base/javax.net.ssl=ALL-UNNAMED--add-opens java.base/sun.net.util=ALL-UNNAMED--add-opens java.base/sun.reflect.annotation=ALL-UNNAMED--add-opens java.base/jdk.internal.vm=ALL-UNNAMED......--add-opens java.base/sun.misc=ALL-UNNAMED--add-opens java.compiler/javax.annotation.processing=ALL-UNNAMED--add-opens java.desktop/java.applet=ALL-UNNAMED--add-opens java.desktop/java.awt=ALL-UNNAMED......--add-opens java.datatransfer/java.awt.datatransfer=ALL-UNNAMED......--add-opens java.desktop/java.beans=ALL-UNNAMED--add-opens java.desktop/java.beans.beancontext=ALL-UNNAMED--add-opens java.desktop/javax.accessibility=ALL-UNNAMED--add-opens java.desktop/javax.imageio=ALL-UNNAMED......--add-opens java.desktop/javax.print=ALL-UNNAMED......--add-opens java.desktop/javax.sound.midi=ALL-UNNAMED......--add-opens java.desktop/javax.swing=ALL-UNNAMED...... --add-opens java.sql/java.sql=ALL-UNNAMED--add-opens java.net.http/java.net.http=ALL-UNNAMED--add-opens java.compiler/javax.lang.model=ALL-UNNAMED......--add-opens java.management/javax.management=ALL-UNNAMED......--add-opens java.management/java.lang.management=ALL-UNNAMED--add-opens java.management/sun.management=ALL-UNNAMED--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED--add-opens java.management.rmi/javax.management.remote.rmi=ALL-UNNAMED--add-opens java.naming/javax.naming=ALL-UNNAMED......--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED--add-opens java.rmi/java.rmi=ALL-UNNAMED......--add-opens java.scripting/javax.script=ALL-UNNAMED--add-opens java.security.jgss/org.ietf.jgss=ALL-UNNAMED--add-opens java.security.jgss/javax.security.auth.kerberos=ALL-UNNAMED--add-opens java.security.sasl/javax.security.sasl=ALL-UNNAMED--add-opens java.smartcardio/javax.smartcardio=ALL-UNNAMED--add-opens java.sql/javax.sql=ALL-UNNAMED--add-opens java.sql.rowset/javax.sql.rowset=ALL-UNNAMED......--add-opens java.compiler/javax.tools=ALL-UNNAMED--add-opens java.transaction.xa/javax.transaction.xa=ALL-UNNAMED--add-opens java.instrument/java.lang.instrument=ALL-UNNAMED--add-opens java.xml/javax.xml=ALL-UNNAMED......--add-opens java.xml/org.xml.sax=ALL-UNNAMED......--add-opens java.xml/jdk.xml.internal=ALL-UNNAMED--add-opens jdk.accessibility/com.sun.java.accessibility.util=ALL-UNNAMED--add-opens jdk.jdi/com.sun.jdi=ALL-UNNAMED......--add-opens jdk.httpserver/com.sun.net.httpserver=ALL-UNNAMED--add-opens jdk.httpserver/com.sun.net.httpserver.spi=ALL-UNNAMED--add-opens jdk.sctp/com.sun.nio.sctp=ALL-UNNAMED--add-opens jdk.security.auth/com.sun.security.auth=ALL-UNNAMED--add-opens jdk.security.auth/com.sun.security.auth.callback=ALL-UNNAMED......--add-opens jdk.compiler/com.sun.source.doctree=ALL-UNNAMED--add-opens jdk.compiler/com.sun.source.tree=ALL-UNNAMED--add-opens jdk.compiler/com.sun.source.util=ALL-UNNAMED--add-opens jdk.attach/com.sun.tools.attach=ALL-UNNAMED--add-opens jdk.attach/com.sun.tools.attach.spi=ALL-UNNAMED--add-opens jdk.compiler/com.sun.tools.javac=ALL-UNNAMED--add-opens jdk.jconsole/com.sun.tools.jconsole=ALL-UNNAMED--add-opens jdk.management/com.sun.management=ALL-UNNAMED--add-opens jdk.management.jfr/jdk.management.jfr=ALL-UNNAMED--add-opens jdk.dynalink/jdk.dynalink=ALL-UNNAMED......--add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED--add-opens jdk.javadoc/jdk.javadoc.doclet=ALL-UNNAMED--add-opens jdk.jfr/jdk.jfr=ALL-UNNAMED--add-opens jdk.jfr/jdk.jfr.consumer=ALL-UNNAMED--add-opens jdk.jshell/jdk.jshell=ALL-UNNAMED......--add-opens jdk.net/jdk.net=ALL-UNNAMED--add-opens jdk.net/jdk.nio=ALL-UNNAMED--add-opens jdk.nio.mapmode/jdk.nio.mapmode=ALL-UNNAMED--add-opens jdk.jartool/jdk.security.jarsigner=ALL-UNNAMED--add-opens jdk.jsobject/netscape.javascript=ALL-UNNAMED
复制代码


依赖包兼容性问题:


  • 问题原因:部分二方、三方依赖包依赖于 JDK21 中已变更甚至移除的 API 导致运行报错。如下图是低版本 lombok 访问的字段被删除了。


Plain Textjava: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'java.lang.RuntimeException: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'        at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:168)        at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)        at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)        at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:237)        at org.jetbrains.jps.javac.ExternalJavacProcess.compile(ExternalJavacProcess.java:196)        at org.jetbrains.jps.javac.ExternalJavacProcess.access$400(ExternalJavacProcess.java:30)        at org.jetbrains.jps.javac.ExternalJavacProcess$CompilationRequestsHandler$1.run(ExternalJavacProcess.java:269)        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)        at java.base/java.lang.Thread.run(Thread.java:1583)
复制代码


  • 解决方案:根据依赖兼容性问题的实际影响和社区支持情况,又可以分为 4 类:

  • 可忽略的兼容性问题:工具扫描提示有兼容性问题,但实际运行不影响,可忽略,比如 springframework 相关包内的问题。

  • 可统一处理的问题:这类问题可以通过在升级过程中统一通过参数配置来解决,无需开发额外处理。比如通过 -Djava.locale.providers=COMPAT 统一处理 CLDR_DATE_FORMAT 规则扫描出来的问题;

  • 直接升级依赖版本:对于大部分存在兼容性问题的三方包中,基本上是因为版本太旧导致的,通过升级到最新的开源版本可解决。这类较多,比如 byte-buddy 需升级到 1.14.3 以上等等。

  • 二次开发:对于自研的二方包和一些在社区最新版本中也不支持 JDK21 的三方包,需要通过二次开发来解决兼容性问题,如 hive 相关的依赖包。


参数变更问题:


  • 问题原因:部分 JVM 参数在新版本中被修改或废弃,如 -XX:+UseParNewGC、-XX:+PrintGC 被弃用,

  • -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 替代了旧版的

  • -XX:InitialRAMFraction、-XX:MaxRAMFraction 和 -XX:MinRAMFraction 等等。

  • 解决方案:梳理参数变更映射表,在升级前后对这些参数进行替换,对于删除的参数则需要统一剔除。


升级工具优化


EMT4J 报告优化


通过原始的 EMT4J 的规则配置,一个项目的扫描报告中往往会产出数百甚至上千条各类告警,涉及 API 变更、内部访问、过时用法等。下图是一个项目的扫描结果,可以看出这个项目被发现存在 2000+ 个兼容性问题,实际其中需要开发同学处理的问题只有十几个。面对报告中的海量信息,业务团队难以迅速定位必需治理的问题点,一个项目往往需要修改半天甚至一天才能改完。



为此,我们对 EMT4J 进行了二次开发和定制优化,去除一些没有必要的检测规则。去除的规则主要可以归为 2 类:


  • 可以在升级过程统一处理的问题:如去掉 cldr-date-format、cldr-calendar-getfirstdayofweek 规则扫描,直接在启动参数上默认加上 -Djava.locale.providers=COMPAT 即可;

  • 确认无影响的问题:如 springframework 相关包的规则扫描,具体包如下


Plain Text"spring-core|spring-context|spring-beans|spring-webflux|spring-web|spring-tx|spring-test|spring-oxm|spring-orm|spring-context-support|spring-websocket|spring-webmvc|spring-messaging|spring-jms|spring-jdbc|spring-jcl|spring-expression|spring-aspects|spring-aop", "$version.ge('5.1')"
复制代码


改造后,EMT4J 的扫描报告简洁明了,只显示需要开发处理的问题。优化后的报告如下图所示。



升级向导开发


为提升整个升级专项的工程效率与一线研发体验,我们自主研发了“JDK 升级向导”,将其作为公司各项目 JDK 升级的统一入口和过程管控中枢。升级向导实现了端到端的自动化覆盖,极大降低升级门槛与出错概率。


在升级过程中,各项目开发人员只需选择目标项目,点击 JDK21 升级,即可跳转到升级向导页面,开始升级流程。下图是升级向导主页面,从图中可以看出升级向导涵盖了服务变更发布全流程,它会自动完成兼容性参数检测与修正、代码扫描与分析、Dockerfile 的适配调整以及各个环境的构建部署,每个步骤中开发人员只需确认即可,除了代码兼容性修订以外,其它都无需人为改动代码。


若升级过程中发现意料外的问题或业务表现偏差,平台还提供一键回退机制,可随时通过界面操作,其代码和配置都会自动还原至上一个稳定版本,做到“过程可视、风险可控、升级无忧”。通过升级向导的建设,我们实现了升级路径标准化、工具化与自动化,大幅提升了团队的协同效率和升级信心。



分批推进


本次 JDK 升级,我们先做了前期的试点,精选了一些简单项目和典型的复杂项目,这样既能够快速打通整体升级流程,也有助于在早期主动发现和解决潜在的复杂兼容性问题,为大规模推广打下基础。但在实际推进过程中,即便试点较充分,仍然会遇到一些预期之外的问题,特别是随着更多项目的逐步上线,也暴露出一些线下难以发现的隐蔽情况。举个例子:


  • 问题描述:dubbo 异步调用出现 ClassNotFoundException 异常。调用代码如:


CompletableFuturexxxFuture = CompletableFuture.supplyAsync(SupplierWrapper.of(() -> xxxApi.findxxxId(xxx.getId())));
复制代码


  • 问题原因:CompletableFuture 的内部执行代码中,根据当前实例的处理器核心数的多少会调用不同的方法:

  • 核数大于等于 3 的情况下使用 ForkJoinPool.commonPool()

  • 核数小于 3 的情况下使用 new ThreadPerTaskExecutor()


而 JDK9 以后为了修正 tomcat 使用 commonPool 内存泄露问题,将 commonPool 指向 systemClassLoader,导致异步情况下获取不到 Spring 的 classLoader 加载的类,发生 ClassNotFound 问题。


  • 问题难点:当机器实例核心数小于 3 的情况下就不会出现该问题,而线下环境较多是小于 2 核的机器,因此未能发现该问题,上线后才出现。

  • 解决方案:

  • 临时方案:启动参数增加 -Djava.util.concurrent.ForkJoinPool.common.parallelism=1,这样就不会调用 ForkJoinPool.commonPool();

  • 最终方案: 通过字节码方式对 classLoader 进行重新设置,改回 JDK 9 以前的实现方式。


正是因为考虑到会出现像这样前期试点无法覆盖的问题,我们最终采用了分批推进策略。试点后,把所有项目按优先级和影响范围分三批,每批上线前都确认上一批已经平稳运行一段时间。这样一旦新问题暴露,我们也能有充足时间定位和迭代方案,避免风险扩散,保证核心业务和高优系统的稳定。


整个分批推进中,每项升级都会自动做回归测试,按灰度 - 监控 - 正式上线的顺序推进,质量有保障,遇到紧急情况还能一键回滚,尽量将风险和影响降到最低。


升级效果与收益


经过有序、标准化的升级流程实践,我们成功顺利了 660 个项目从 JDK8 到 JDK21 的平滑升级。以下是已完成的项目列表。



此次升级在交付效率、风险控制、系统性能和成本效益等多个方面取得了显著成效,具体体现在:


升级过程高效稳定


  • 配合方投入少:本次 JDK21 升级通过 JDK21 升级文档 + 自动化升级向导 + JDK21 升级指导 & 答疑群 的方式,减少了配合方的人力和资源投入。扣除常规发布时间,各服务做适配改造只需投入 10~30 分钟;

  • 专项完成时间短:通过建立标准化的升级流程和自动化升级向导,大幅减少升级难度和工作量,660 个项目从开始试点到升级完成,仅耗时 3 个月;

  • 0 故障:完备的问题指导文档、及时专业的过程指导、易用的升级向导、以及出问题后的一键回退能力,保障了整个升级过程的稳定性。整个专项完成 660 个服务升级上线,0 故障。


性能提升 & IT 降本


内存大幅下降:在所有升级的 660 个服务中,从 jvm 内存指标上看,平均内存占用下降 51.33%,总计节省数 T 的内存。下图是其中一个服务 JDK 升级前后的堆内存使用情况。



CPU 使用率下降:在 CPU 密集型的服务和原本服务 CPU 使用率较高的情况下,CPU 收益会比较明显。总的来看,约 13% 的服务 CPU 有下降,下降幅度在 10%~30%。


整体吞吐提升:算法侧的服务表现比较明显,其 RT 降低约 10~30%。


总结与展望


本次全公司项目大规模的从 JDK8 到 JDK21 的升级,不仅消除了历史技术债务、提升了系统性能与资源利用效率,更在自动化建设、流程标准化和团队协作模式上有了较大突破。通过统一的升级向导、深度的兼容性扫描和多轮分批实践,我们成功将数百个核心服务的平滑升级变为可大规模复制的工程实践,实现了“高效率、零故障、无感知”的升级体验。


随着云原生、AI 等新技术形态不断涌现,底层技术栈的健康与演进将成为企业核心竞争力之一。我们将继续沉淀升级自动化、兼容性治理和工程协同经验,不断优化平台治理能力,为后续 Spring Boot 主干版本升级、云原生转型等更多技术挑战做好准备。相信有了本次体系化工程升级的经验积累,企业 IT 架构将具备更强弹性和适应力,能够更从容地应对未来的技术变革和业务创新。

2025-06-23 10:2431

评论

发布
暂无评论

如何处理消息丢失问题?

JavaEdge

1月月更

(1-14/14) 首位销售人员

mtfelix

300天创作 2022Y300P

TDSQL PG版企业级分布式数据库技术创新实践

腾讯云数据库

tdsql 国产数据库

TDSQL-C for PostgreSQL 主从架构详解

腾讯云数据库

tdsql 国产数据库

2021 OceanBase 开源半年度报告 | 不忘初心,感恩同行

OceanBase 数据库

开发者 报告 OceanBase 开源 OceanBase 社区版

我相信:没有解不开的难题|ONES 人物

万事ONES

架构实战训练营-模块7-作业

温安适

「架构实战营」

ReactNative进阶(二十三):Javascript 严格模式详解

No Silver Bullet

React Native 1月月更

虎符研究院深入解读Web3.0未来趋势 盘点代表性项目

区块链前沿News

Web Hoo虎符 虎符研究院 虎符平台 3.0

Go 语言快速入门指南:Go 并发初识

宇宙之一粟

golang 并发 Go 语言 1月月更

Scrum Master需要具备哪些能力和经验

华为云开发者联盟

Scrum 敏捷 团队 教练 Scrum Master

TDSQL | 将企业级分布式数据库做到极致

腾讯云数据库

tdsql 国产数据库

创业老兵李峻的新征程|ONES 人物

万事ONES

前端开发之JQuery的综合应用

@零度

jquery 前端开发

知识库进化论 | 华创资本对话 ONES & 为知笔记创始人

万事ONES

混沌工程之 Linux 网络故障模拟工具TC

zuozewei

Linux 混沌工程 1月月更

为什么HashMap会产生死循环?

王磊

java开发之Redis的使用规范

@零度

redis JAVA开发

Serverless 背景下,一部分“前端工程师”会转变为“应用交付工程师”

杨成功

Serverless 架构 前端

Jetpack—LiveData组件的缺陷以及应对策略

vivo互联网技术

android livedata JetPack 移动应用开发

低代码实现探索(二十二)如何构建一个可以看的懂的系统

零道云-混合式低代码平台

Android技术分享| 自定义View实现使用更方便的SeekBar

anyRTC开发者

android 音视频 移动开发 白板 SeekBar

Hive SQL底层执行原理

五分钟学大数据

Hive SQL 1月月更

GIS :元宇宙未来发展的有力技术支撑

华为云开发者联盟

AI GIS 虚拟世界 数字孪生 云宇宙

前端使用 zx 库在 Node 中编写 Shell 脚本

devpoint

node.js Shell 1月月更 zx.js

龙蜥社区一周动态 | 1.10-1.14

OpenAnolis小助手

Linux 开源 社群

Chrome插件:摸鱼倒计时、每日摸鱼时间统计,奋斗逼、卷王必备,用于减少摸鱼时间和频率

OBKoro1

效率 开源 效率工具 chrome扩展 高效率

ONES CTO 冯斌|如何低成本地做出高质量决策

万事ONES

软件设计——依赖倒置

苏州程序大白

架构师

1月月更|推荐学java——Spring之AOP

逆锋起笔

spring SSM框架 spring aop 依赖注入 面向切面编程

企业级 JDK 升级实战:660 个项目从 JDK8 到 JDK21 的零故障升级之路_架构_InfoQ精选文章