原文地址
尼恩 说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如美团、拼多多、极兔、有赞、希音的面试资格,遇到一几个很重要的面试题:
- 你们用什么版本的jdk?为啥你们还在用java8?
- 聊一聊不同版本的jdk的新特性?
- 现在大厂都用JDK21 ZGC了,你了解 ZGC 垃圾回收器的底层原理?ZGC 的浮动垃圾,是怎么处理的?
- 聊一聊JDK21 ZGC 如何升级?
这里,尼恩借助一系列互联网的顶级案例,给大家做一下系统化、体系化梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。
大厂(转转、携程、京东)都用分代ZGC,卡顿降低20倍,吞吐量提升4倍。分代ZGC 这么牛?底层原理是什么?
这里,尼恩找到一个漂亮的生产级案例:朴朴科技平台组 《企业级 JDK 升级实战:660 个项目从 JDK8 到 JDK21 的零故障升级之路》。注意,这又一个非常 牛逼的工业级、生产级案例。
注意,这些案例,并不是尼恩的原创,仅仅是尼恩在备课的过程中,在互联网查找资料的时候,收集起来的,供大家学习和交流使用。
特别声明,学习材料来自互联网, 不做商业使用,尼恩也仅仅作为学习材料使用。
由于没有原作者的联系方式,这里没有原作者的授权,如果原作者不同意尼恩的发布, 给尼恩留言就行,尼恩即刻从《技术自由圈》公众号扯下来。
为了帮助大家拿到大厂/架构offer,实现人生逆袭, 尼恩团队之前还给大家写过 五大 GC 学习圣经
第一大 gc 学习圣经:cms
第二大 gc 学习圣经: G1
尼恩提示,其实 :G1 是最复杂的
第3、4 大 gc 学习圣经: ZGC
《分代 ZGC 圣经:分代ZGC 底层原理和 大厂实战案例学习》
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
朴朴660个项目从 JDK8 到 JDK21 的零故障升级之路
在企业系统的架构升级过程中,是否要升级 JDK 版本一直是个让人纠结的问题。
新版本JDK21 /JDK24 虽然性能更好、功能更强,但也 带来兼容性问题+ 不小的改动成本。
这篇文章, 58 转转 如何在不影响线上服务的情况下,用 6 个月时间,顺利将 660 个项目从 JDK8 升级到 JDK21 的全过程。
660 个项目从 JDK8 升级到 JDK21 的全过程
一、背景与动机
现状困境
多年来, 后端 Java 开发一直使用 JDK8。
但随着业务越来越多,技术也在不断进步,JDK8 逐渐出现了一些问题,比如:
困境一:性能与资源瓶颈问题
随着业务越来越多,服务器的内存和 CPU 使用也越来越紧张。
一些关键的服务只能靠不断增加机器配置来维持正常运行,但这不仅浪费资源,也让运维的工作越来越难做。
困境2:生态兼容受限
现在 Java 社区主要在用高版本的 JDK 推动技术发展。
很多主流的新项目,比如 Spring Boot 3.x、Kafka 4.0 等,都已经不再支持 JDK8。
这导致依赖升级变得更麻烦,也更容易出现组件之间不兼容的问题。
困境3:技术持续演化受阻
JDK8 缺少新版本 Java 提供的一些实用功能、工具和监控支持,团队在开发时用起来不方便,也难跟上新的开发模式,影响了效率和技术创新
困境4:安全可控性下降
JDK8 虽然过去一直稳定,但不代表现在和未来依然安全。随着黑客攻击手段升级,加上行业对合规性的要求越来越高,老版本系统将面临更大的安全风险和维护难度。继续使用旧版本会带来更高的成本和隐患。
结论是:迟早要 启动了 JDK 升级专项工作
所以,大家迟早要 启动了 JDK 升级专项工作,目标是从根本上解决问题,甩掉老旧技术的包袱,提升企业的技术能力和系统安全性。
JDK 21 升级价值
价值1:性能提升
JDK21 在多个关键方面做了优化,比如 JIT 编译、线程管理、垃圾回收、对象和内存的处理等。这些改进让程序运行得更快、更省资源。
JDK21 在多个底层机制上进行了改进,使得程序运行更快、内存更省,整体性能相比 JDK8 有显著提升。
在使用 G1 垃圾回收器的情况下,JDK21 和 JDK8 的表现差异:
- 性能提升明显:JDK21 的吞吐量比 JDK8 高了将近 50%。
- 内存占用更低:JDK21 使用的内存减少了大约 60%。
这两项改进意味着,在同样的硬件条件下,JDK21 能跑更多的任务,而且效率更高。
价值2:新的语言特性和功能支持
- 新增了像“记录类Record”、“模式匹配Pattern Matching”、“switch 表达式”等新功能 和 现代化 Java 语言特性,让代码更清晰、写起来也更快;
- 支持虚拟线程(Virtual Threads) ,让并发编程更轻松,不用再手动管理大量线程;极大简化高并发场景下的线程管理,实现更轻量级的并发编程;
- 增强了诊断和监控工具,方便排查线上问题,也更容易做自动化运维。
价值3:生态与未来演进能力
- 跟上主流开源项目和技术框架的发展,确保能顺利对接行业常用做法,避免技术落后;
- 持续获取社区的安全更新、新功能和使用建议,保持系统稳定和先进。
二、风险与挑战
升级带来好处,但也伴随着挑战。
- 升级有好处,但不是小事。
- 提前识别JDK升级可能带来的问题。
- 分析实际执行过程中可能遇到的困难。
- 做好准备,确保升级顺利进行。
为了顺利推进整个升级工作,需要 提前梳理了JDK升级可能带来的风险,并对实际操作中可能出现的问题做了分析。
风险1:兼容性风险:
-
模块限制变多:JDK9 加入了模块系统,限制了反射访问非公开类和方法的能力,可能导致一些运行时出错。
-
第三方库可能不兼容:有些老的二方包或三方库还没更新和适配新 JDK,可能会在调用 API 或生成字节码时出问题。
-
旧 API 被淘汰:JDK8 中的一些类或方法在新版中被废弃或删除,如果代码里用了这些内容,编译或运行会失败。
-
标准库行为有变化:有些类或方法虽然名字一样,但功能或返回结果变了,可能导致业务逻辑出错。
-
测试工具可能不支持:比如 JUnit、Mockito 等测试框架的老版本不支持高版本 JDK,需要升级到对应版本才能正常跑单测。
-
构建脚本也要更新:Maven、Gradle 或 CI/CD 中的脚本如果没有适配新 JDK,打包发布过程可能会出错,需要检查并调整。
风险2:运维风险:
- 手动操作容易出错:升级过程中需要修改很多配置,如果靠人来操作,容易遗漏或出错,导致升级失败或系统异常;
- 环境配置要统一:升级完成后,各个环境中使用的参数和JVM设置要保持一致,否则可能会出现奇怪的问题;
这个流程图展示了升级过程中的两个关键点:
一是人为操作容易出错,二是配置不一致可能带来后续问题。
风险3:隐藏风险:
-
功能不变:升级 JDK 版本时,很多依赖包也要跟着升级,怎么保证系统功能和以前一样不出问题;
-
隐藏问题难发现:新版本可能带入一些不容易察觉或排查的 bug;
- 流程从评估开始,逐步检查依赖、测试功能;
- 如果功能不一致,需要修复并重新验证;
- 确保整个升级过程可控、可回滚。
升级挑战
升级 JDK 版本不只是找出风险那么简单。
在真正实施迁移时,特别是在大规模系统中,还会遇到很多工程和组织上的难题。
总结了一下,这次 JDK 升级主要面临以下几个方面的挑战:
(1) 兼容性问题
不同 JDK 版本之间,有些功能可能被废弃或行为发生了变化,老项目可能无法直接运行。
(2) 依赖库支持
项目中用到的各种第三方库是否支持新 JDK?如果不支持,就需要找替代方案或者推动更新。
(3) 性能与稳定性验证
新版本虽然通常性能更好,但实际运行效果需要测试确认,避免上线后出问题。
(4) 团队协作与沟通
多个团队一起参与升级,协调工作不容易,需要统一节奏、明确分工。
(5) 回滚机制准备
如果升级过程中出现问题,有没有快速恢复的方案?这也是必须考虑的。
依赖包量大且关系复杂
项目里用了很多自己开发的包、第三方库和插件,这些依赖之间关系复杂。
不同应用用的版本还不一样,有些底层库甚至已经没人维护了。
这导致在做兼容测试和适配时,工作量非常大。
项目体量庞大
公司里需要升级的重要应用有 660 多个,包括订单、库存、支付和数据分析等核心业务系统。
这么多项目要一起升级,让版本适配、功能测试和上线推进变得非常复杂
跨多团队协助
升级项目涉及几十个业务团队,以及基础架构、运维、测试等多个支持部门。需要统一协调资源、同步信息、把控进度,这对跨部门协作和流程管理来说是个不小的考验。
三、升级目标
经过前面的分析,团队已经清楚这次系统升级的难度和挑战。
为了更好地控制进度、减少风险,并确保实现预期效果,为这次升级工作设定了以下几个主要目标:
按期完成
在6个月内完成以下工作:前期调研、工具开发,以及将660多个项目从JDK8升级到JDK21。
- 前期调研:了解升级难点、环境依赖等。
- 工具建设:开发辅助升级的工具,提高效率。
- 项目升级:实际操作,把项目从 JDK8 升级到 JDK21。
- 目标:总共完成超过 660 个项目升级。
无 P3 以上故障
在升级过程中,要保证业务正常运行。即使升级出现问题,影响业务的严重程度最高不能超过 P3 级别,并且最多只能出现 1 次这样的问题。
- 升级不是随便操作,每一步都要确保不影响用户正常使用。
- 如果出问题,也要控制在“不太严重”的范围内(也就是 P3 及以下),而且最多只能有一次这样的小问题。
- 整个过程要有监控,发现问题要及时处理。
高效低成本交付
升级整个项目控制在1小时内完成,操作尽量自动化、一键式,减少人工参与和团队协作的负担。
升级过程开发无感
这次升级由平台团队主导,业务开发团队只需少量配合,不影响日常开发、项目交付和用户体验。
- 平台团队负责主要工作,业务团队只需做少量对接或验证。
- 整个过程对业务运作是“透明”的,不会造成干扰。
四、升级流程与落地实践
为了确保升级目标能顺利实现,制定了一个清晰的策略:风险提前发现、工具先准备好、分批次推进。
核心思路是:在前期就把可能遇到的问题找出来并解决掉,再借助自动化和标准化的工具,降低团队协作和技术操作的难度,确保在两个季度内高质量完成升级,而且不影响业务运行。
同时,也准备了自动回退机制,一旦出现意外情况,可以快速恢复,保障业务不中断。
上图展示了整个升级流程的关键节点。其中最核心的三个环节是:
(1) 全面识别和处理兼容性问题
(2) 优化升级工具
(3) 分阶段、分批次推进升级
下面将分别介绍这三个关键环节。
兼容性扫描与方案制定
在前期准备中发现,要扫描的对象很多,而且它们之间有很多依赖关系,靠人工根本做不完。
所以引入了一个开源工具 EMT4J,结合工具自动扫描和人工验证的方式,对以下几类关键对象进行了全面检查:
1. 服务依赖库
用 EMT4J 工具对公司所有服务使用的插件、二方包、三方包进行兼容性扫描,一共扫了 2800 多个依赖包。
结果发现有 130 个包存在兼容问题。
2. 测试与质量平台
检查了常用的测试框架(比如 JUnit、Mockito)和代码质量工具 Sonar 在 JDK21 下是否能正常运行。
结果发现这些工具都需要升级才能支持新版本的 JDK。
3. CICD 相关
查看 Maven 构建脚本、CI/CD 流水线以及调试工具(如 Arthas)是否兼容 JDK21。
发现构建脚本需要调整,Arthas 的部分功能也存在问题,需要更新版本。
4. 监控与告警体系
检查了应用在 JDK21 下的指标采集、链路追踪和告警系统是否正常。这部分没有发现兼容性问题。
整个过程是先通过工具自动扫描发现问题,再配合人工确认,最终整理出需要处理的兼容性问题清单,并给出对应的修复方向。
问题归类与解决方案
在升级 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
- 依赖包问题 → 分类处理(忽略/配置/升级/修改)
- 参数变化 → 替换或删除旧参数
整个过程可通过流程图清晰展示,便于团队协作和问题追踪。
升级工具优化
EMT4J 报告优化
使用 EMT4J 工具扫描项目兼容性问题时,原始配置会报告几百甚至上千条告警,包括 API 变化、内部调用、过时用法等。
比如下图这个项目,扫描出 2000 多个问题,但真正需要开发处理的只有十几个。
面对这么多信息,业务团队很难快速找出要改的地方,一个项目常常要花半天甚至一天来排查。
为了解决这个问题,对 EMT4J 做了定制优化,去掉了一些不必要的检查规则。
主要去掉了两类:
1. 升级时可以统一解决的问题
有些问题是可以通过统一配置解决的,不需要逐个检查。例如:
cldr-date-format
和cldr-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 的报告变得清晰很多,只显示真正需要开发处理的问题。
如下图所示:
这样改造之后,不仅提升了效率,也降低了开发修改的成本。
“JDK 升级向导”的工具开发
为了提高 JDK 升级的效率和研发体验,开发了一个叫“JDK 升级向导”的工具,作为公司所有项目升级 JDK 的统一入口和流程管理平台。
这个工具实现了从头到尾的自动化操作,大大降低了升级难度和出错的可能性。
在使用过程中,开发人员只需要选择要升级的项目,点击“升级到 JDK21”,就会跳转到升级向导页面开始流程。
如下图所示,整个升级过程包括:参数检查与修正、代码扫描分析、Dockerfile 调整、环境构建部署等步骤。
除了部分代码需要人工确认兼容性外,其他操作都由系统自动完成。
如果在升级过程中发现问题或业务异常,平台还支持一键回退功能,可以快速恢复到上一个稳定版本,做到“看得见流程、控得住风险、升得上去心不慌”。
通过这个升级向导,把 JDK 升级流程标准化、工具化和自动化,提升了团队协作效率,也增强了大家对升级的信心。
分批推进
这次升级了 JDK,先选了一些简单和复杂的项目做试点。这样既能快速跑通整个流程,也能提前发现一些兼容性问题,为后续全面升级打好基础。
虽然前期试点比较充分,但在实际推进中还是遇到了一些没预料到的问题。
特别是当更多项目上线后,暴露出一些在线下环境不容易发现的隐藏问题。
比如:
问题示例:dubbo 异步调用报 ClassNotFoundException
代码示例:
CompletableFuturexxxFuture = CompletableFuture.supplyAsync(SupplierWrapper.of(() -> xxxApi.findxxxId(xxx.getId())));
原因分析:
CompletableFuture
在执行时会根据 CPU 核心数选择不同的线程池:- 如果核心数 ≥ 3,使用
ForkJoinPool.commonPool()
- 如果核心数 < 3,使用
new ThreadPerTaskExecutor()
- 如果核心数 ≥ 3,使用
- JDK9 开始,commonPool 使用的是系统类加载器(systemClassLoader),而不是 Spring 的类加载器。
- 导致异步调用时找不到 Spring 加载的类,出现
ClassNotFoundException
为什么线下没发现?
- 线下测试机器大多是单核或双核,不会触发 commonPool,所以问题没暴露出来。
解决办法:
- 临时方案:启动参数加
-Djava.util.concurrent.ForkJoinPool.common.parallelism=1
,避免使用 commonPool - 长期方案:通过字节码修改,把 classLoader 恢复成 JDK9 前的行为
升级策略:分批上线,控制风险
考虑到类似问题可能在后期才暴露,采用了分批上线的方式:
(1) 把所有项目按优先级分成三批
(2) 每一批上线前,确保上一批已经稳定运行一段时间
(3) 每次上线都走灰度发布、监控观察、正式上线的流程
(4) 所有变更都会自动回归测试,有问题可以一键回滚
这样做的目的是控制风险范围,保障核心业务不受影响。
通过小范围试点发现问题,再分批上线控制风险,配合自动化测试和灰度发布机制,确保 JDK 升级过程安全可控。
五、升级效果与收益
通过一套规范、有序的升级流程,顺利完成了 660 个项目从 JDK8 到 JDK21 的升级,整个过程平稳无故障。
以下是已经完成升级的部分项目列表。
这次升级在多个方面带来了明显的好处,包括:
- 提升交付效率:升级过程更加快速、顺畅
- 降低风险:流程标准化,问题可控
- 优化系统性能:新版本JDK运行更快、更稳定
- 节省成本:整体资源消耗减少,运维更高效
升级过程高效稳定
- 配合方投入少:这次 JDK21 升级主要靠升级文档、自动化工具和答疑群支持,配合方几乎不用额外花太多时间和人力。每个服务适配只需要 10~30 分钟;
- 专项完成快:通过统一的流程和自动升级工具,升级变得简单高效。660 个项目从开始到完成,只用了 3 个月;
- 全程无故障:有详细的问题处理指南、实时指导、好用的升级工具,还有出问题能一键回退的功能,整个过程顺利完成了 660 个服务的升级,没有出现故障。
性能提升 & IT 降本
内存使用明显减少
在升级了 JDK 的 660 个服务中,平均内存占用下降了 51.33%,整体节省了几 TB 的内存。下图展示了一个服务在升级前后的内存使用对比:
CPU 使用率降低
对于计算密集型或原本 CPU 占用高的服务,升级后 CPU 使用率有明显改善。大约 13% 的服务 CPU 占比下降,降幅在 10%~30% 之间。
吞吐能力提升
特别是在算法类服务中,响应时间(RT)减少了 10%~30%,整体处理效率提高。
这个流程展示了从 JDK 升级到性能优化的整体逻辑:
(1) 完成 JDK 升级
(2) 监控关键指标变化
(3) 观察内存、CPU 和吞吐等方面的优化效果
六、总结与展望
这次全公司范围内的 JDK 从 8 升级到 21 的项目,不仅解决了老技术问题,还提升了系统性能和资源使用效率。更重要的是,在自动化工具、流程规范和团队协作方面也有了明显进步。
通过统一的升级指南、深入的兼容性检查和多轮分批实施,成功把几百个核心服务的升级过程变得简单可复制,做到了“高效、无故障、用户无感知”。
随着云原生、AI 等新技术的发展,底层技术栈的更新能力越来越重要。
朴朴会继续优化自动升级、兼容性处理和团队协作机制,提升平台整体治理水平,为后续 Spring Boot 版本升级、云原生转型等挑战做好准备。
有了这次经验,的技术架构将更有弹性,也能更灵活应对未来的技术变化和业务需求。