写点什么

JDK11 升级 JDK17 最全实践干货来了

  • 2024-06-25
    北京
  • 本文字数:6342 字

    阅读完需:约 21 分钟

1、前言

如果你仍在使用 JDK8,那你是否曾经遇到过 OutOfMemoryError 的问题?你是否曾经为 JVM 的调优问题感到困扰?本篇文章将为你介绍一种能够提供百倍性能提升的垃圾回收器,也许能够解决你的问题。


上篇文章给大家带来了JDK8升级JDK11的最全实践,相信大家阅读后已经对 JDK11 有了比较深入的了解。2021 年 9 月 14 日,Oracle 发布了可以长期支持的 JDK17 版本,那么从 JDK11 到 JDK17,到底带来了哪些特性呢?亚毫秒级的 ZGC 效果到底怎么样呢?值得我们升级吗?而且升级过程会遇到哪些问题呢?带着这些问题,本篇文章将带来完整的 JDK11 升级 JDK17 最全实践。

2、为什么升级 JDK17

1)长期支持版本


JDK17 是 Oracle 官方在 2021 年 9 月 14 日发布的一个长期支持(LTS)版本,意味着它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性。


2)性能提升


更好的垃圾回收器。综合评估,从 Java 8 升级到 Java 11,G1GC 平均速度提升 16.1%,ParallelGC 为 4.5% 从 Java 11 升级到 Java 17,G1GC 平均速度提升 8.66%,ParallelGC 为 6.54%基于OptaPlanner的用例基准测试表明)


最大的亮点是带来了稳定版的 ZGC 垃圾回收器,达到亚毫秒级停顿。


3)新语法和特性


Switch 表达式简化、Text Blocks 文本块、instanceof 的模式匹配升级和 NullPointerException 提示信息改进等


4)支持最新的技术和框架


Spring framework6 和 Spring Boot3 都默认使用 Java 17 作为最低版本

3、升级后压测效果

先给出结论: 1、JDK17 相对于 JDK8 和 JDK11,所有垃圾回收器的性能都有很明显的提升,特别是稳定版的 ZGC 垃圾回收器 ****2、不论任何机器配置下,都推荐使用 ZGC,ZGC 的停顿时间达到亚毫秒级,吞吐量也比较高


我在 JDOS 平台上选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用 JDK8、JDK11 和 JDK17 进行部署和压测。


整个压测过程限时 60 分钟,用 180 个虚拟用户并发请求一个接口,每次接口请求都创建 512Kb 的数据。最终产出不同 GC 回收器的各项指标数据,来分析 GC 的性能提升效果。


以下是压测的性能情况:


4、OracleJDK 和 OpenJDK 的选择

2021 年 9 月,Oracle 宣布 JDK17 可以免费商用,直到下一个 LTS 版本之后继续提供整整一年,同时 Oracle 将继续按照自 Java 9 以来的相同版本和时间表提供 GPL 下的 Oracle OpenJDK 版本。


2023 年 9 月,OracleJDK 发布了新的 LTS 版本 JDK21,这就意味着从 2024 年 9 月开始,在生产环境使用 OracleJDK17 将需要付费。



参考: https://www.oracle.com/hk/java/technologies/downloads/#java17


OracleJDK 和 OpenJDK 这两个之间没有真正的技术差别,因为针对 Oracle JDK 构建过程是基于 OpenJDK 的。自从 JDK11 开始,OracleJDK 和 OpenJDK 在功能上基本相同,所以推荐使用 OpenJDK17 或其他开源的 JDK 版本,这些开源版本都是基于 OpenJDK 构建并提供长期支持的,比如:AdoptOpenJDKRedHatOpenJDK。



官方参考: https://blogs.oracle.com/java/post/oracle-jdk-releases-for-java-11-and-later

5、JDK11 到 JDK17 带来了哪些新特性

5.1、JVM 改进

1、ZGC 垃圾回收器从实验性功能更改为正式产品功能,从 JDK11 引入以来,经过持续的迭代升级,目前已经足够稳定。需要手动开启,开启方式:-XX:+UseZGC


2、G1 垃圾回收器仍然作为默认垃圾回收器,进行改进升级,主要包括可中止的混合收集集合、NUMA 可识别内存分配等


3、JDK14 开始删除 CMS 垃圾回收器


4、JDK14 开始弃用 ParallelScavenge 和 SerialOld GC 的组合使用


5、JDK15 禁用偏向锁,默认禁用:-XX:+UseBiasedLocking


6、NullPointerException 提示信息改进


JDK14 以前的出现 NullPointerException 时,只能定位到所在异常行,无法定位具体是哪个变量。改进后的 NullPointerException,可以清晰描述具体变量,提升了空指针异常的可读性。


5.2、新语法特性

5.2.1、Switch 表达式简化

switch 表达式带来了简化式的编码方式,提供了新的分支切换方式,即 -> 符号,右则表达式方法体在执行完分支方法之后,自动结束 switch 分支,同时 -> 右则方法块中可以是表达式、代码块或者是手动抛出的异常


参考: https://openjdk.org/jeps/361


传统写法



新写法


5.2.2、Text Blocks 文本块(5 星推荐)

参考: https://openjdk.org/jeps/378


通过编写 """,来减少转义字符和换行符,达到简化代码和提高代码可读性的目的


5.2.3、Record 类型

参考: https://openjdk.org/jeps/395


record 是 JDK 14 引入的关键字,用于声明不可变的数据类。它适用于存储纯粹的值类型数据,如接口传输数据、坐标点和只读的日志记录。与 lombok 相比,record 简化了定义纯粹数据类型的过程。由于 record 类是不可变的,成员变量只能设置一次且无法更改,无需提供显式的 setter() 方法。


1、定义 Point 类,使用关键字 record,未定义 get/set



2、查看编译后的字节码文件




3、使用 Point 类


5.2.4、instanceof 的模式匹配升级

•instanceof 类型判断再也不需要强制转换


参考: https://openjdk.org/jeps/394


5.2.5、密封的类和接口

参考: https://openjdk.org/jeps/409


JDK15 开始,引入了 sealed 普通类或接口类,这些类只允许被指定的类或者 interface 进行扩展和实现。


使用修饰符 sealed,您可以将一个类声明为密封类。密封的类使用关键字 permits 列出可以直接扩展它的类。子类可以是最终的,非密封的或密封的


比较实用的一个特性,可以用来限制类的层次结构


5.2.6、其他优化和升级

感兴趣的同学,推荐阅读 OpenJDK 官方文档说明,从 JDK11 到 JDK17 的改动: https://openjdk.org/projects/jdk/17/jeps-since-jdk-11

6、升级步骤

6.1、JDK 选择

OpenJDK17 下载:https://jdk.java.net/archive/


行云镜像:jdt-base-tomcat/java-jdt-centos7.4-openjdk-17.0.2-tomcat8.0.53

6.2、pom 编译配置升级

maven 编译所需 JDK 升级至 17


<properties>    <maven.compiler.source>17</maven.compiler.source>    <maven.compiler.target>17</maven.compiler.target></properties>
复制代码

6.3、SpringBoot 升级

SpringBoot 版本升级到 2.7.15,Spring 版本升级为 5.3.29


为什么不升级到 SpringBoot3?


Spring Boot 3.0 最低要求 Java 17,SpringBoot3.0 带来了很多变化,和 SpringBoot2 差异较大。 考虑到公司很多中间件都是基于 SpringBoot2 构建的,所以此处推荐升级到 SpringBoot2 的最高版本 2.7.15。


POM 升级


<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.15</version></parent>
复制代码


也可以通过设置 dependencyManagement 的方式:


<properties>    <!-- 框架版本配置-->    <springboot-version>2.7.15</springboot-version>    <springframework.version>5.3.29</springframework.version></properties>  
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${springboot-version}</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-framework-bom</artifactId> <version>${springframework.version}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies></dependencyManagement>
复制代码


参考:


spring 升级指南: https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions


springboot 版本官网: https://spring.io/projects/spring-boot#learn


循环依赖问题


SpringBoot 升级到 2.7.15 后,如果应用中存在循环依赖的问题,启动时会报如下错误:



原因: 官方文档不鼓励循环依赖引用,默认情况下是禁止的


解决方案:


第一种:推荐更新应用中 bean 的依赖关系来解决


第二种:配置文件中加入以下配置,为了和旧版本保持一致,此配置推荐添加


#放开循环依赖spring.main.allow-circular-references=true
复制代码

6.4、常用中间件升级

6.4.1、Lombok 版本升级到****1.18.20 以上

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version></dependency>
复制代码


如果不升级,编译时会报错如下:


6.4.2、swgger 问题,springfox3.0.0 和 springboot2.7 版本不兼容

异常:


Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
复制代码


解决方案:


properties 配置(感谢 zhaoyongping8 验证提供):


spring.mvc.pathmatch.matching-strategy=ant_path_matcher


/** * 增加如下配置可解决Spring Boot 2.7.15 与Swagger 3.0.0 不兼容问题 **/@Beanpublic BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {return new BeanPostProcessor() {
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); }return bean;}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) { List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList()); mappings.clear(); mappings.addAll(copy); }
@SuppressWarnings("unchecked")private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {try { Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); field.setAccessible(true);return (List<RequestMappingInfoHandlerMapping>) field.get(bean); } catch (IllegalArgumentException | IllegalAccessException e) {throw new IllegalStateException(e); } } };}
复制代码


参考:https://developer.aliyun.com/article/950787

6.4.3、AKS 升级(针对直接从 JDK8 升级的情况)

异常: Causedby: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException


原因: Java11 删除了 Java EE modules,其中就包括 java.xml.bind (JAXB)。


解决方案:


手动引入如下包即可


<!-- API, java.xml.bind module --> <dependency>      <groupId>jakarta.xml.bind</groupId>      <artifactId>jakarta.xml.bind-api</artifactId>      <version>2.3.2</version></dependency> <!-- Runtime, com.sun.xml.bind module --><dependency>       <groupId>org.glassfish.jaxb</groupId>       <artifactId>jaxb-runtime</artifactId>       <version>2.3.2</version></dependency>
复制代码

6.4.4、Concrete 配置中心阻塞升级

新版本 2.2.1 已修复此问题


使用 Concrete 时,启动时异常:


 Unable to make field private static final java.lang.reflect.Method jdk.proxy2.$Proxy97.m0 accessible:  module jdk.proxy2 does not "opens jdk.proxy2" to unnamed module @61d47554
复制代码


原因:


分析下 Concrete 报错的原因,如下图,包内 com.wangyin.concrete.spring.ConcreteConfigProcessor#postProcessAfterInitialization(212 行)的实现逻辑




解决方案:


1、在 JVM 启动参数中设置--add-opens jdk.proxy2 来开启私有字段的访问,但因为动态代理生成的包名是随机不明确的,所以这种方案不可行。JDK 官方文档也明确表示不支持访问动态代理内部的随机字段。官方说明:https://cr.openjdk.org/~mr/jigsaw/spec/api/java/lang/reflect/Proxy.html


2、代码修改,只需把 f.setAccessible(true) 移到 Modifier.isStatic(f.getModifiers()) 的判断下方即可。原因是方法 Modifier.isStatic(f.getModifiers()) 本来就要跳过静态字段,这样修改直接避免了访问。推动 concrete 团队修复问题

6.5、JVM 启动参数配置

6.5.1、开启 ZGC

启动参数中配置: -XX:+UseZGC


6.5.2、不同中间件所需启动参数

升级 JDK17 后,项目启动时可能会遇到如下两种类型的异常:


1、cannot access class sun.util.calendar.ZoneInfo (in module java.base) because module java.base does not export sun.util.calendar to unnamed module @0x2611f533


2、Unable to make field final int java.math.BigInteger.signum accessible: module java.base does not "opens java.math" to unnamed module @525f1e4e


异常原因:


自从 JDK9 中引入了模块化功能后,再到 JDK17,对于包扫描和反射的权限控制更加的严格。常见的库比如(Spring)大量用到包扫描和反射,所以常出现此错误。


解决方案:


一个粗暴的解决办法是将没开放的 module 强制对外开放,即保持和 Java9 之前的版本一致。


•--add-exports 导出包,意味着其中的所有公共类型和成员都可以在编译和运行时访问。


•--add-opens 打开包,意味着其中的所有类型和成员(不仅是公共类型)都可以在运行时访问。


主要区别在于--add-opens允许“深度反射”,即非公共成员的访问,才可以调用setAccessible(true)


参考: https://stackoverflow.com/questions/44056405/whats-the-difference-between-add-exports-and-add-opens-in-java-9


SGM 需要加入:


--add-opens java.management/java.lang.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED
复制代码


R2M 需要加入:


--add-opens java.base/java.time=ALL-UNNAMED
复制代码


Ducc 需要加入:


--add-opens java.base/java.util.concurrent=ALL-UNNAMED--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED--add-opens java.base/java.security=ALL-UNNAMED--add-opens java.base/jdk.internal.loader=ALL-UNNAMED--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 
复制代码


AKS 需要加入:


--add-exports java.base/sun.security.action=ALL-UNNAMED--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/java.math=ALL-UNNAMED--add-opens java.base/java.util=ALL-UNNAMED--add-opens java.base/sun.util.calendar=ALL-UNNAMED
复制代码

6.6、启动后的验证

1.推荐先升级 JDK11,再到 JDK17,一边升级一边进行验证观察


2.观察日志是否有异常,特别是上面说到的启动时异常


3.观察监控类软件,比如 SGM、UMP 等监控是否正常


4.推荐逐步有序切量,并做好常态化压测,防止影响核心业务


5.升级完成后,最好能做个全流程的功能测试,防止功能异常

7、总结

1、升级后,除了可以使用新的语法特性,最大的亮点是可以使用亚毫秒级停顿的 GC 性能(至少百倍的 GC 性能提升),所以 强烈建议升级到 JDK17


2、整个升级过程并不复杂,主要涉及到中间件版本的升级和启动参数的配置


如果还停留在 JDK8,推荐先升级 JDK11,再到 JDK17,具体升级步骤先参考我的上篇文章“JDK8升级JDK11最全实践干货来了”,再参考本章中的升级步骤。


希望以上分享可以给大家带来实际的帮助,升级过程中如果遇到问题,欢迎大家在评论区回复。


系列文章:


JDK8升级JDK11最全实践干货来了


JDK11升级JDK17最全实践干货来了


你还在“垃圾”调优?快来看看JDK17的ZGC如何解放双手


JDK17实践升级经验汇总

发布于: 刚刚阅读数: 6
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
JDK11升级JDK17最全实践干货来了_京东科技开发者_InfoQ写作社区