JDK11升级JDK17最全实践干货来了
mhr18 2025-01-05 18:58 17 浏览 0 评论
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构建并提供长期支持的,比如:AdoptOpenJDK、RedHatOpenJDK。?
官方参考: 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 不兼容问题
**/
@Bean
public 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实践升级经验汇总?
相关推荐
- 一文读懂Prometheus架构监控(prometheus监控哪些指标)
-
介绍Prometheus是一个系统监控和警报工具包。它是用Go编写的,由Soundcloud构建,并于2016年作为继Kubernetes之后的第二个托管项目加入云原生计算基金会(C...
- Spring Boot 3.x 新特性详解:从基础到高级实战
-
1.SpringBoot3.x简介与核心特性1.1SpringBoot3.x新特性概览SpringBoot3.x是建立在SpringFramework6.0基础上的重大版...
- 「技术分享」猪八戒基于Quartz分布式调度平台实践
-
点击原文:【技术分享】猪八戒基于Quartz分布式调度平台实践点击关注“八戒技术团队”,阅读更多技术干货1.背景介绍1.1业务场景调度任务是我们日常开发中非常经典的一个场景,我们时常会需要用到一些不...
- 14. 常用框架与工具(使用的框架)
-
本章深入解析Go生态中的核心开发框架与工具链,结合性能调优与工程化实践,提供高效开发方案。14.1Web框架(Gin,Echo)14.1.1Gin高性能实践//中间件链优化router:=...
- SpringBoot整合MyBatis-Plus:从入门到精通
-
一、MyBatis-Plus基础介绍1.1MyBatis-Plus核心概念MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提...
- Seata源码—5.全局事务的创建与返回处理
-
大纲1.Seata开启分布式事务的流程总结2.Seata生成全局事务ID的雪花算法源码3.生成xid以及对全局事务会话进行持久化的源码4.全局事务会话数据持久化的实现源码5.SeataServer创...
- Java开发200+个学习知识路线-史上最全(框架篇)
-
1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...
- OpenResty 入门指南:从基础到动态路由实战
-
一、引言1.1OpenResty简介OpenResty是一款基于Nginx的高性能Web平台,通过集成Lua脚本和丰富的模块,将Nginx从静态反向代理转变为可动态编程的应用平台...
- 你还在为 Spring Boot3 分布式锁实现发愁?一文教你轻松搞定!
-
作为互联网大厂后端开发人员,在项目开发过程中,你有没有遇到过这样的问题:多个服务实例同时访问共享资源,导致数据不一致、业务逻辑混乱?没错,这就是分布式环境下常见的并发问题,而分布式锁就是解决这类问题的...
- 近2万字详解JAVA NIO2文件操作,过瘾
-
原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。从classpath中读取过文件的人,都知道需要写一些读取流的方法,很是繁琐。最近使用IDEA在打出.这个符号的时候,一行代...
- 学习MVC之租房网站(十二)-缓存和静态页面
-
在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用、发邮件,并将通过UEditor上传的图片保存到云存储。在项目的最后,再学习优化网站性能的一些技术:缓存和...
- Linux系统下运行c++程序(linux怎么运行c++文件)
-
引言为什么要在Linux下写程序?需要更多关于Linux下c++开发的资料请后台私信【架构】获取分享资料包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdf...
- 2022正确的java学习顺序(文末送java福利)
-
对于刚学习java的人来说,可能最大的问题是不知道学习方向,每天学了什么第二天就忘了,而课堂的讲解也是很片面的。今天我结合我的学习路线为大家讲解下最基础的学习路线,真心希望能帮到迷茫的小伙伴。(有很多...
- 一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)
-
前言15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有幸去了网易。但是要...
- 多商户商城系统开发全流程解析(多商户商城源码免费下载)
-
在数字化商业浪潮中,多商户商城系统成为众多企业拓展电商业务的关键选择。这类系统允许众多商家在同一平台销售商品,不仅丰富了商品种类,还为消费者带来更多样的购物体验。不过,开发一个多商户商城系统是个复杂的...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
oracle数据库查询Sql语句是否使用索引及常见的索引失效的情况
-
Java SE Development Kit 8u441下载地址【windows版本】
-
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)