企业级JDK升级思路分享(一)JDK11升级到JDK17

前言

我司Java应用有10多个,凡是涉及到基础架构及其配置的升级,例如:操作系统,Apache,Tomcat,pom.xml中的第三方依赖,以及JDK等。都要进行全平台统一升级和维护。所以一些看似简单的工作,由于涉及的Java应用之多,代码之复杂,反而需要我们更加细心、谨慎。

我司于去年将JDK从JDK8升级到JDK11,今年计划将JDK从JDK11升级到JDK17。升级到JDK17的任务由我负责,以下为升级过程中的一些思考和做法。该思路不局限于JDK11升级到JDK17,之后的JDK升级也同样适用。

如果你们公司现在也面临将JDK8升级到JDK11或JDK17的情况,推荐阅读京东技术团队关于升级JDK的文章。总的来说,我们升级过程中遇到的问题和他们遇到的非常像,相似度在80%。可能看完他们的文章,都不需要读我的文章了。

  1. JDK8升级到JDK11
  2. JDK11升级到JDK17

应该使用哪个供应商的JDK?

之所以有这个问题是因为Java被Oracle收购之后,Oracle JDK 8u202版本之后的JDK开始收费。如果你们公司现在用的还是Oracle JDK 8u202之前的版本,那么在升级时势必要面临抉择。

目前市面上JDK的供应商很多,有Oracle,RedHat,还有开源的Adoptium Eclipse Temurin。这些JDK之前到底有什么不同?建议阅读一个韩国工程师写的 Which Version of JDK Should I Use?,有助于各位了解目前JDK的供应商和各个版本的JDK的不同。

由于我们不考虑商用,以开源的优先,所以我们选择Adoptium Eclipse Temurin,并根据其Release Roadmap以及实际情况来选择进行后续的升级。

JDK17到底快了多少?

上面京东技术团队的文章中有简要描述,这里再出几个文章链接

  1. How much faster is Java 17?
  2. GC progress from JDK 8 to JDK 17
  3. 一个快速比较各版本JVM之间不同的网页小工具

有关GC的几篇文章

  1. Bending pause times to your will with Generational ZGC
  2. netflix关于Java的文章
  3. 有关ZGC的表现的文章

升级思路

阅读官方升级指南

仔细阅读Oracle官方的升级到JDK17的迁移指南。虽然我们使用的是开源的JDK而不是付费的Oralce JDK,但对于一些JDK标准的变动,二者是保持一致的。

升级到JDK17后一些代码不兼容的问题

我们碰到了一个关于空指针异常消息判断的不兼容

在JDK11中

public static int parseInt(String s, int radix)
                throws NumberFormatException
    {
        if (s == null) {
            throw new NumberFormatException("null");
        }	
}

在JDK17中

public static int parseInt(String s, int radix)
                throws NumberFormatException
    {
        if (s == null) {
            throw new NumberFormatException("Cannot parse null string");
        }
}

有些开发人员,在处理异常时根据异常的字符串进行了判断,此时升级到JDK17,此判断无效,导致代码出现bug。

Fix: 修改判断空指针的逻辑,不允许根据异常消息的字符串进行判断

升级到JDK17后如何确保代码中的反射可以正常调用?

在阅读Oracle JDK17升级指南时,我发现了一段描述在这里插入图片描述
对于还在使用JDK8的同学来说,要理解这段话,需要了解一个小背景。在JDK9开始,JDK推出了Module System的概念,即对JDK 的包进行了更细致的切分,并对代码反射的访问权限进一步严格限制。

不了解Module System的可以看一下京东技术团队 JDK8升级到JDK11有关模块化的说明或自行Google相关知识。

JDK9-15虽然已经采用模块化,但是对于一些代码反射的访问权限并没有限制很严格,用户依然可以通过添加 --illegal-access=permit 来使得反射可以顺利进行。

从JDK16开始默认禁止所有非法反射访问 --illegal-access=deny(即如果想要通过反射访问某个包,该包在模块文件module-info中必须使用opens指令声明)。但是JDK16中还是可以修改为permit

从JDK17开始–illegal-access将不再有效(见JEP 403),JVM启动时会忽略该选项。

找出哪些第三方库无法执行反射

了解了上述背景知识之后,再来看实际遇到的问题。这么多Java应用都在使用JDK11,代码之多,使用的第三方依赖之多,如何找出来哪些代码的反射是在JDK17中不被允许的呢?

  1. 为JVM添加启动参数--illegal-access=warn 并将该参数应用到所有Java应用的测试以及产品环境中,测试环境每天有大量的auto smoke test帮我们做测试。 定期分析测试环境和产品环境日志,看日志中是否有类似日志
    WARNING: Illegal reflective access by
    
  2. 步骤1 应用到产品环境一段时间后,如果有上述警告日志,则根据实际情况添加–add-opens参数,例如
    --add-opens=java.desktop/javax.swing=ALL-UNNAMED
    
    也可以不加=号,JDK官方关于此写法的讨论
    --add-opens java.desktop/javax.swing=ALL-UNNAMED
    
    但通常建议加上=号
    然后继续分析日志,看是否还有步骤1的警告日志
  3. 经过上述2个步骤一段时间的验证,将测试环境和产品环境JVM启动参数改为--illegal-access=deny。并继续分析日志,以确保代码中没有反射失败的情况。

将运行时JDK改为JDK17

我们的策略是:

  1. 先将测试环境RunTime JDK改为17并运行一段时间;如果测试环境运行稳定,则将产品环境RunTime JDK改为逐渐切换为17
  2. 但使用Jenkins build项目时,编译时JDK依旧保持JDK11
  3. 简而言之,把JDK11编译出来的包放在JDK17中跑

优化Build Java应用时的Jenkins Job

目前build java应用之后,tomcat使用的JDK是hard code在相关的jenkins job里的。

这种写法不利于当前以及后续JDK升级的测试。原因是:这些jenkins job的配置基本由devops维护,我们实际使用者如果有什么需求只需要给他们提就可以了,他们会给我们创建单独的job。

在了解了他们的实际工作过程之后,我发现完全没必要copy、创建新的jenkins job,只需要在原有的jenkins job上添加一个参数,例如:RUNTIME_JDK,build项目时将该参数传给shell脚本,在构建完docker镜像之后,启动Tomcat之前,执行一个switchJDK.sh 即可完成JDK的切换。

这样做的好处是

  1. 从长期来讲,devops的工作量反而是减轻了且有助于他们的代码维护,他们只需要定期把未来要使用JDK版本纳入到我们使用的自动以的docker镜像中,就无需介入JDK的升级工作。
  2. 对于我们实际升级JDK的人以及其他开发或QA同事来说,有了这个参数,前期JDK升级时,如果测试环境有什么问题,可以迅速切换回正在使用的JDK,不会影响当前工单的smoke test。

将编译时JDK改为JDK17

产品环境运行时JDK改为17之后,运行一段时间,如果产品环境稳定。则将编译时JDK改为JDK17。这一步主要是pom.xml的改变

<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>

这个改变合并到主分支之后,build该项目的jenkins job需要同时做切换。指定编译时JDK为17

通知开发切换为JDK17

上述改动合并到主分支之后,JDK就完成了升级,需要通知开发下载和产品、测试环境一样的JDK 版本并切换,并修改IDEA中有关Java版本、编译的配置

写在最后

对所有Java应用,从JDK8到JDK11,所有的验证、测试、分批部署到产品、最终完成升级,我们用了接近半年时间完成升级。预计JDK17会比较顺利,可能4个月左右完成升级。

上面这些事情看起来不复杂,但整个过程要想保证对服务不造成影响,需要我们非常细心、谨慎,以及和其他团队充分配合才能一起完成这件事,这更考验我们除了编程之外的技能。

最后以一句话结尾:

只有那些有耐心做好简单事情的人,才能获得轻松完成困难事情的技能 ​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值