Java8真的变老!JDK21时代到了。看看朴朴660 个项目从 JDK8 到 JDK21 的零故障升级之路

原文地址

原文地址

尼恩 说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如美团、拼多多、极兔、有赞、希音的面试资格,遇到一几个很重要的面试题:

  • 你们用什么版本的jdk?为啥你们还在用java8?
  • 聊一聊不同版本的jdk的新特性?
  • 现在大厂都用JDK21 ZGC了,你了解 ZGC 垃圾回收器的底层原理?ZGC 的浮动垃圾,是怎么处理的?
  • 聊一聊JDK21 ZGC 如何升级?

这里,尼恩借助一系列互联网的顶级案例,给大家做一下系统化、体系化梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

大厂(转转、携程、京东)都用分代ZGC,卡顿降低20倍,吞吐量提升4倍。分代ZGC 这么牛?底层原理是什么?

这里,尼恩找到一个漂亮的生产级案例:朴朴科技平台组 《企业级 JDK 升级实战:660 个项目从 JDK8 到 JDK21 的零故障升级之路》。注意,这又一个非常 牛逼的工业级、生产级案例

注意,这些案例,并不是尼恩的原创,仅仅是尼恩在备课的过程中,在互联网查找资料的时候,收集起来的,供大家学习和交流使用。

特别声明,学习材料来自互联网, 不做商业使用,尼恩也仅仅作为学习材料使用。

由于没有原作者的联系方式,这里没有原作者的授权,如果原作者不同意尼恩的发布, 给尼恩留言就行,尼恩即刻从《技术自由圈》公众号扯下来。

为了帮助大家拿到大厂/架构offer,实现人生逆袭, 尼恩团队之前还给大家写过 五大 GC 学习圣经

第一大 gc 学习圣经:cms

《cms圣经:cms 底层原理和调优实战》

第二大 gc 学习圣经: G1

《G1圣经:G1 底层原理和调优实战》

京东面试: 垃圾回收器 CMS、G1 区别是什么?

尼恩提示,其实 :G1 是最复杂的

第3、4 大 gc 学习圣经: ZGC

《ZGC 圣经:ZGC 底层原理和调优实战》

《分代 ZGC 圣经:分代ZGC 底层原理和 大厂实战案例学习》

阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案

朴朴660个项目从 JDK8 到 JDK21 的零故障升级之路

在企业系统的架构升级过程中,是否要升级 JDK 版本一直是个让人纠结的问题。

新版本JDK21 /JDK24 虽然性能更好、功能更强,但也 带来兼容性问题+ 不小的改动成本。

这篇文章, 58 转转 如何在不影响线上服务的情况下,用 6 个月时间,顺利将 660 个项目从 JDK8 升级到 JDK21 的全过程。

660 个项目从 JDK8 升级到 JDK21 的全过程

Mermaid

一、背景与动机

现状困境

多年来, 后端 Java 开发一直使用 JDK8。

但随着业务越来越多,技术也在不断进步,JDK8 逐渐出现了一些问题,比如:

困境一:性能与资源瓶颈问题

随着业务越来越多,服务器的内存和 CPU 使用也越来越紧张。

一些关键的服务只能靠不断增加机器配置来维持正常运行,但这不仅浪费资源,也让运维的工作越来越难做。

Mermaid

困境2:生态兼容受限

现在 Java 社区主要在用高版本的 JDK 推动技术发展。

很多主流的新项目,比如 Spring Boot 3.x、Kafka 4.0 等,都已经不再支持 JDK8。

这导致依赖升级变得更麻烦,也更容易出现组件之间不兼容的问题。

Mermaid

困境3:技术持续演化受阻

JDK8 缺少新版本 Java 提供的一些实用功能、工具和监控支持,团队在开发时用起来不方便,也难跟上新的开发模式,影响了效率和技术创新

Mermaid

困境4:安全可控性下降

JDK8 虽然过去一直稳定,但不代表现在和未来依然安全。随着黑客攻击手段升级,加上行业对合规性的要求越来越高,老版本系统将面临更大的安全风险和维护难度。继续使用旧版本会带来更高的成本和隐患。

结论是:迟早要 启动了 JDK 升级专项工作

所以,大家迟早要 启动了 JDK 升级专项工作,目标是从根本上解决问题,甩掉老旧技术的包袱,提升企业的技术能力和系统安全性。

JDK 21 升级价值

价值1:性能提升

JDK21 在多个关键方面做了优化,比如 JIT 编译、线程管理、垃圾回收、对象和内存的处理等。这些改进让程序运行得更快、更省资源。

Mermaid

JDK21 在多个底层机制上进行了改进,使得程序运行更快、内存更省,整体性能相比 JDK8 有显著提升。

在使用 G1 垃圾回收器的情况下,JDK21 和 JDK8 的表现差异:

  • 性能提升明显:JDK21 的吞吐量比 JDK8 高了将近 50%。
  • 内存占用更低:JDK21 使用的内存减少了大约 60%。

这两项改进意味着,在同样的硬件条件下,JDK21 能跑更多的任务,而且效率更高。

img

img

价值2:新的语言特性和功能支持

  • 新增了像“记录类Record”、“模式匹配Pattern Matching”、“switch 表达式”等新功能 和 现代化 Java 语言特性,让代码更清晰、写起来也更快;
  • 支持虚拟线程(Virtual Threads) ,让并发编程更轻松,不用再手动管理大量线程;极大简化高并发场景下的线程管理,实现更轻量级的并发编程;
  • 增强了诊断和监控工具,方便排查线上问题,也更容易做自动化运维。

Mermaid

价值3:生态与未来演进能力

  • 跟上主流开源项目和技术框架的发展,确保能顺利对接行业常用做法,避免技术落后;
  • 持续获取社区的安全更新、新功能和使用建议,保持系统稳定和先进。

Mermaid

二、风险与挑战

升级带来好处,但也伴随着挑战。

  • 升级有好处,但不是小事。
  • 提前识别JDK升级可能带来的问题。
  • 分析实际执行过程中可能遇到的困难。
  • 做好准备,确保升级顺利进行。

为了顺利推进整个升级工作,需要 提前梳理了JDK升级可能带来的风险,并对实际操作中可能出现的问题做了分析。

Mermaid

风险1:兼容性风险:

  • 模块限制变多:JDK9 加入了模块系统,限制了反射访问非公开类和方法的能力,可能导致一些运行时出错。

  • 第三方库可能不兼容:有些老的二方包或三方库还没更新和适配新 JDK,可能会在调用 API 或生成字节码时出问题。

  • 旧 API 被淘汰:JDK8 中的一些类或方法在新版中被废弃或删除,如果代码里用了这些内容,编译或运行会失败。

  • 标准库行为有变化:有些类或方法虽然名字一样,但功能或返回结果变了,可能导致业务逻辑出错。

  • 测试工具可能不支持:比如 JUnit、Mockito 等测试框架的老版本不支持高版本 JDK,需要升级到对应版本才能正常跑单测。

  • 构建脚本也要更新:Maven、Gradle 或 CI/CD 中的脚本如果没有适配新 JDK,打包发布过程可能会出错,需要检查并调整。

Mermaid

风险2:运维风险:

  • 手动操作容易出错:升级过程中需要修改很多配置,如果靠人来操作,容易遗漏或出错,导致升级失败或系统异常;
  • 环境配置要统一:升级完成后,各个环境中使用的参数和JVM设置要保持一致,否则可能会出现奇怪的问题;

Mermaid

这个流程图展示了升级过程中的两个关键点:

一是人为操作容易出错,二是配置不一致可能带来后续问题。

风险3:隐藏风险:

  • 功能不变:升级 JDK 版本时,很多依赖包也要跟着升级,怎么保证系统功能和以前一样不出问题;

  • 隐藏问题难发现:新版本可能带入一些不容易察觉或排查的 bug;

Mermaid

  • 流程从评估开始,逐步检查依赖、测试功能;
  • 如果功能不一致,需要修复并重新验证;
  • 确保整个升级过程可控、可回滚。

升级挑战

升级 JDK 版本不只是找出风险那么简单。

在真正实施迁移时,特别是在大规模系统中,还会遇到很多工程和组织上的难题。

总结了一下,这次 JDK 升级主要面临以下几个方面的挑战:

(1) 兼容性问题

不同 JDK 版本之间,有些功能可能被废弃或行为发生了变化,老项目可能无法直接运行。

(2) 依赖库支持

项目中用到的各种第三方库是否支持新 JDK?如果不支持,就需要找替代方案或者推动更新。

(3) 性能与稳定性验证

新版本虽然通常性能更好,但实际运行效果需要测试确认,避免上线后出问题。

(4) 团队协作与沟通

多个团队一起参与升级,协调工作不容易,需要统一节奏、明确分工。

(5) 回滚机制准备

如果升级过程中出现问题,有没有快速恢复的方案?这也是必须考虑的。

依赖包量大且关系复杂

项目里用了很多自己开发的包、第三方库和插件,这些依赖之间关系复杂。

不同应用用的版本还不一样,有些底层库甚至已经没人维护了。

这导致在做兼容测试和适配时,工作量非常大。

项目体量庞大

公司里需要升级的重要应用有 660 多个,包括订单、库存、支付和数据分析等核心业务系统。

这么多项目要一起升级,让版本适配、功能测试和上线推进变得非常复杂

Mermaid

跨多团队协助

升级项目涉及几十个业务团队,以及基础架构、运维、测试等多个支持部门。需要统一协调资源、同步信息、把控进度,这对跨部门协作和流程管理来说是个不小的考验。

Mermaid

三、升级目标

经过前面的分析,团队已经清楚这次系统升级的难度和挑战。

为了更好地控制进度、减少风险,并确保实现预期效果,为这次升级工作设定了以下几个主要目标:

按期完成

在6个月内完成以下工作:前期调研、工具开发,以及将660多个项目从JDK8升级到JDK21。

Mermaid

  • 前期调研:了解升级难点、环境依赖等。
  • 工具建设:开发辅助升级的工具,提高效率。
  • 项目升级:实际操作,把项目从 JDK8 升级到 JDK21。
  • 目标:总共完成超过 660 个项目升级。

无 P3 以上故障

在升级过程中,要保证业务正常运行。即使升级出现问题,影响业务的严重程度最高不能超过 P3 级别,并且最多只能出现 1 次这样的问题。

Mermaid

  • 升级不是随便操作,每一步都要确保不影响用户正常使用。
  • 如果出问题,也要控制在“不太严重”的范围内(也就是 P3 及以下),而且最多只能有一次这样的小问题。
  • 整个过程要有监控,发现问题要及时处理。

高效低成本交付

升级整个项目控制在1小时内完成,操作尽量自动化、一键式,减少人工参与和团队协作的负担。

升级过程开发无感

这次升级由平台团队主导,业务开发团队只需少量配合,不影响日常开发、项目交付和用户体验。

Mermaid

  • 平台团队负责主要工作,业务团队只需做少量对接或验证。
  • 整个过程对业务运作是“透明”的,不会造成干扰。

四、升级流程与落地实践

为了确保升级目标能顺利实现,制定了一个清晰的策略:风险提前发现、工具先准备好、分批次推进

核心思路是:在前期就把可能遇到的问题找出来并解决掉,再借助自动化和标准化的工具,降低团队协作和技术操作的难度,确保在两个季度内高质量完成升级,而且不影响业务运行。

同时,也准备了自动回退机制,一旦出现意外情况,可以快速恢复,保障业务不中断。

img

上图展示了整个升级流程的关键节点。其中最核心的三个环节是:

(1) 全面识别和处理兼容性问题

(2) 优化升级工具

(3) 分阶段、分批次推进升级

Mermaid

下面将分别介绍这三个关键环节。

兼容性扫描与方案制定

在前期准备中发现,要扫描的对象很多,而且它们之间有很多依赖关系,靠人工根本做不完。

所以引入了一个开源工具 EMT4J,结合工具自动扫描和人工验证的方式,对以下几类关键对象进行了全面检查:

1. 服务依赖库

用 EMT4J 工具对公司所有服务使用的插件、二方包、三方包进行兼容性扫描,一共扫了 2800 多个依赖包。

结果发现有 130 个包存在兼容问题。

2. 测试与质量平台

检查了常用的测试框架(比如 JUnit、Mockito)和代码质量工具 Sonar 在 JDK21 下是否能正常运行。

结果发现这些工具都需要升级才能支持新版本的 JDK。

3. CICD 相关

查看 Maven 构建脚本、CI/CD 流水线以及调试工具(如 Arthas)是否兼容 JDK21。

发现构建脚本需要调整,Arthas 的部分功能也存在问题,需要更新版本。

4. 监控与告警体系

检查了应用在 JDK21 下的指标采集、链路追踪和告警系统是否正常。这部分没有发现兼容性问题。

整个过程是先通过工具自动扫描发现问题,再配合人工确认,最终整理出需要处理的兼容性问题清单,并给出对应的修复方向。

Mermaid

问题归类与解决方案

在升级 JDK 从 8 到 21 的过程中,发现了一些兼容性问题,主要可以归为三类:反射访问限制、依赖包不兼容、参数配置变化。下面分别说明每类问题的原因和解决办法。


1、反射访问问题

原因:

JDK9 引入了模块化机制,限制了对非公开类或方法的反射访问。

以前可以通过反射调用 protected 或 private 成员,现在会抛出 InaccessibleObjectException 错误。


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)

解决方案:

使用 JVM 参数 --add-opens 来开放特定模块下的包,允许反射访问。

注意:每个包都要单独配置,不会自动包含子包。比如 --add-opens java.base/java.lang=ALL-UNNAMED 只开放 java.lang 包,并不会包含 java.lang.annotation 等子包,所以每个要被反射访问的包都需要独立开放,

示例配置如下(部分省略):


--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
...
--add-opens jdk.compiler/com.sun.tools.javac=ALL-UNNAMED

2、依赖包兼容性问题

原因:

有些第三方库(如 Lombok)依赖了 JDK21 中被修改或删除的 API,导致运行时报错。

解决方案:

根据影响程度分为四类处理方式:

(1) 可忽略的问题:工具提示有风险但不影响运行,比如 Spring 相关包中的某些问题。

(2) 统一配置解决:通过参数统一处理,例如 -Djava.locale.providers=COMPAT

(3) 升级依赖版本:大多数问题是由于依赖版本过旧,升级到最新版即可解决,如 byte-buddy 升级到 1.14.3+。

(4) 二次开发修复:对于自研组件或社区未支持 JDK21 的三方包,需要手动修改代码适配,如 Hive 相关依赖。

3、参数配置项变化

原因:

一些 JVM 启动参数在 JDK21 中被废弃或替换,例如:

  • -XX:+UseParNewGC 被废弃;
  • -XX:InitialRAMFraction 被替换为 -XX:InitialRAMPercentage

解决方案:

整理新旧参数映射表,在升级时进行替换,废弃参数直接移除。

问题归类的总结

升级 JDK 是一个系统性工作,重点在于识别三大类问题并采取对应策略:

  • 反射访问 → 加 --add-opens
  • 依赖包问题 → 分类处理(忽略/配置/升级/修改)
  • 参数变化 → 替换或删除旧参数

整个过程可通过流程图清晰展示,便于团队协作和问题追踪。

Mermaid

升级工具优化

EMT4J 报告优化

使用 EMT4J 工具扫描项目兼容性问题时,原始配置会报告几百甚至上千条告警,包括 API 变化、内部调用、过时用法等。

比如下图这个项目,扫描出 2000 多个问题,但真正需要开发处理的只有十几个。

img

面对这么多信息,业务团队很难快速找出要改的地方,一个项目常常要花半天甚至一天来排查。

为了解决这个问题,对 EMT4J 做了定制优化,去掉了一些不必要的检查规则。

主要去掉了两类:

1. 升级时可以统一解决的问题

有些问题是可以通过统一配置解决的,不需要逐个检查。例如:

  • cldr-date-formatcldr-calendar-getfirstdayofweek 这些规则可以直接忽略,因为只要在启动参数里加上 -Djava.locale.providers=COMPAT 就能解决。
2. 确认不会影响业务的问题

有些规则虽然报了问题,但确认它们不影响实际运行,也可以跳过。

比如 Spring 框架的一些包:


"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')"

这些包在版本大于等于 5.1 的情况下是安全的,所以可以放心地不扫描这些问题。

经过这些优化,EMT4J 的报告变得清晰很多,只显示真正需要开发处理的问题。

如下图所示:

img


这样改造之后,不仅提升了效率,也降低了开发修改的成本。

“JDK 升级向导”的工具开发

为了提高 JDK 升级的效率和研发体验,开发了一个叫“JDK 升级向导”的工具,作为公司所有项目升级 JDK 的统一入口和流程管理平台。

这个工具实现了从头到尾的自动化操作,大大降低了升级难度和出错的可能性。

在使用过程中,开发人员只需要选择要升级的项目,点击“升级到 JDK21”,就会跳转到升级向导页面开始流程。

如下图所示,整个升级过程包括:参数检查与修正、代码扫描分析、Dockerfile 调整、环境构建部署等步骤。

除了部分代码需要人工确认兼容性外,其他操作都由系统自动完成。

如果在升级过程中发现问题或业务异常,平台还支持一键回退功能,可以快速恢复到上一个稳定版本,做到“看得见流程、控得住风险、升得上去心不慌”。

通过这个升级向导,把 JDK 升级流程标准化、工具化和自动化,提升了团队协作效率,也增强了大家对升级的信心。

img

分批推进

这次升级了 JDK,先选了一些简单和复杂的项目做试点。这样既能快速跑通整个流程,也能提前发现一些兼容性问题,为后续全面升级打好基础。

虽然前期试点比较充分,但在实际推进中还是遇到了一些没预料到的问题。

特别是当更多项目上线后,暴露出一些在线下环境不容易发现的隐藏问题。

比如:

问题示例:dubbo 异步调用报 ClassNotFoundException

代码示例


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

原因分析

  • CompletableFuture 在执行时会根据 CPU 核心数选择不同的线程池:
    • 如果核心数 ≥ 3,使用 ForkJoinPool.commonPool()
    • 如果核心数 < 3,使用 new ThreadPerTaskExecutor()
  • JDK9 开始,commonPool 使用的是系统类加载器(systemClassLoader),而不是 Spring 的类加载器。
  • 导致异步调用时找不到 Spring 加载的类,出现 ClassNotFoundException

为什么线下没发现?

  • 线下测试机器大多是单核或双核,不会触发 commonPool,所以问题没暴露出来。

解决办法

  • 临时方案:启动参数加 -Djava.util.concurrent.ForkJoinPool.common.parallelism=1,避免使用 commonPool
  • 长期方案:通过字节码修改,把 classLoader 恢复成 JDK9 前的行为
升级策略:分批上线,控制风险

考虑到类似问题可能在后期才暴露,采用了分批上线的方式:

(1) 把所有项目按优先级分成三批

(2) 每一批上线前,确保上一批已经稳定运行一段时间

(3) 每次上线都走灰度发布、监控观察、正式上线的流程

(4) 所有变更都会自动回归测试,有问题可以一键回滚

这样做的目的是控制风险范围,保障核心业务不受影响。

Mermaid

通过小范围试点发现问题,再分批上线控制风险,配合自动化测试和灰度发布机制,确保 JDK 升级过程安全可控。

五、升级效果与收益

通过一套规范、有序的升级流程,顺利完成了 660 个项目从 JDK8 到 JDK21 的升级,整个过程平稳无故障。

以下是已经完成升级的部分项目列表。

img

这次升级在多个方面带来了明显的好处,包括:

  • 提升交付效率:升级过程更加快速、顺畅
  • 降低风险:流程标准化,问题可控
  • 优化系统性能:新版本JDK运行更快、更稳定
  • 节省成本:整体资源消耗减少,运维更高效

Mermaid

升级过程高效稳定

  • 配合方投入少:这次 JDK21 升级主要靠升级文档、自动化工具和答疑群支持,配合方几乎不用额外花太多时间和人力。每个服务适配只需要 10~30 分钟;
  • 专项完成快:通过统一的流程和自动升级工具,升级变得简单高效。660 个项目从开始到完成,只用了 3 个月;
  • 全程无故障:有详细的问题处理指南、实时指导、好用的升级工具,还有出问题能一键回退的功能,整个过程顺利完成了 660 个服务的升级,没有出现故障。

性能提升 & IT 降本

内存使用明显减少

在升级了 JDK 的 660 个服务中,平均内存占用下降了 51.33%,整体节省了几 TB 的内存。下图展示了一个服务在升级前后的内存使用对比:

img

CPU 使用率降低

对于计算密集型或原本 CPU 占用高的服务,升级后 CPU 使用率有明显改善。大约 13% 的服务 CPU 占比下降,降幅在 10%~30% 之间。

吞吐能力提升

特别是在算法类服务中,响应时间(RT)减少了 10%~30%,整体处理效率提高。

Mermaid

这个流程展示了从 JDK 升级到性能优化的整体逻辑:

(1) 完成 JDK 升级

(2) 监控关键指标变化

(3) 观察内存、CPU 和吞吐等方面的优化效果

六、总结与展望

这次全公司范围内的 JDK 从 8 升级到 21 的项目,不仅解决了老技术问题,还提升了系统性能和资源使用效率。更重要的是,在自动化工具、流程规范和团队协作方面也有了明显进步。

通过统一的升级指南、深入的兼容性检查和多轮分批实施,成功把几百个核心服务的升级过程变得简单可复制,做到了“高效、无故障、用户无感知”。

随着云原生、AI 等新技术的发展,底层技术栈的更新能力越来越重要。

朴朴会继续优化自动升级、兼容性处理和团队协作机制,提升平台整体治理水平,为后续 Spring Boot 版本升级、云原生转型等挑战做好准备。

有了这次经验,的技术架构将更有弹性,也能更灵活应对未来的技术变化和业务需求。

原文地址

原文地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值