一、前言
2021年9月14日,oracle发布了可以长期支持的jdk17版本,那么从jdk11到jdk17,到底带来了哪些特性呢?亚毫秒级的zgc效果到底怎么样呢?值得我们升级吗?而且升级过程会遇到哪些问题呢?带着这些问题,本篇文章将带来完整的jdk11升级jdk17最全实践。
二、为什么升级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作为最低版本
三、升级后压测效果
先给出结论:
jdk17相对于jdk8和jdk11,所有垃圾回收器的性能都有很明显的提升,特别是稳定版的zgc垃圾回收器
不论任何机器配置下,都推荐使用zgc,zgc的停顿时间达到亚毫秒级,吞吐量也比较高
我在jdos平台上选择了不同配置的机器(2c4g、4c8g、8c16g),并分别使用jdk8、jdk11和jdk17进行部署和压测。
整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512kb的数据。最终产出不同gc回收器的各项指标数据,来分析gc的性能提升效果。
以下是压测的性能情况:
四、oraclejdk 和 openjdk 的选择
2021年9月,oracle宣布jdk17可以免费商用,直到下一个 lts 版本之后继续提供整整一年,同时oracle 将继续按照自 java 9 以来的相同版本和时间表提供gpl下的oracle openjdk 版本。
2023年9月,oraclejdk发布了新的lts版本 jdk21,这就意味着从2024年9月开始,在生产环境使用 oraclejdk17 将需要付费。
oraclejdk和openjdk这两个之间没有真正的技术差别,因为针对oracle jdk构建过程是基于openjdk的。自从jdk11开始,oraclejdk和openjdk在功能上基本相同,所以推荐使用 openjdk17 或其他开源的jdk版本,这些开源版本都是基于openjdk构建并提供长期支持的,比如:adoptopenjdk、redhatopenjdk。
五、jdk11到jdk17带来了哪些新特性
5.1、jvm改进
zgc垃圾回收器从实验性功能更改为正式产品功能,从jdk11引入以来,经过持续的迭代升级,目前已经足够稳定。需要手动开启,开启方式:-xx:+usezgc
g1垃圾回收器仍然作为默认垃圾回收器,进行改进升级,主要包括可中止的混合收集集合、numa 可识别内存分配等
jdk14开始删除 cms 垃圾回收器
jdk14开始弃用 parallelscavenge 和 serialold gc 的组合使用
jdk15禁用偏向锁,默认禁用:-xx:+usebiasedlocking
nullpointerexception 提示信息改进
jdk14以前的出现nullpointerexception时,只能定位到所在异常行,无法定位具体是哪个变量。改进后的nullpointerexception,可以清晰描述具体变量,提升了空指针异常的可读性。
5.2、新语法特性
5.2.1、switch表达式简化
switch表达式带来了简化式的编码方式,提供了新的分支切换方式,即 -> 符号,右则表达式方法体在执行完分支方法之后,自动结束 switch 分支,同时 -> 右则方法块中可以是表达式、代码块或者是手动抛出的异常
传统写法
新写法
5.2.2、text blocks文本块
通过编写 ,来减少转义字符和换行符,达到简化代码和提高代码可读性的目的
5.2.3、record类型
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、密封的类和接口
jdk15开始,引入了sealed普通类或接口类,这些类只允许被指定的类或者interface进行扩展和实现。 使用修饰符sealed,您可以将一个类声明为密封类。密封的类使用关键字permits列出可以直接扩展它的类。子类可以是最终的,非密封的或密封的 比较实用的一个特性,可以用来限制类的层次结构
5.2.6、其他优化和升级
感兴趣的同学,推荐阅读openjdk官方文档说明
六、升级步骤
6.1、jdk选择
6.2、pom编译配置升级
maven编译所需jdk升级至17
17 17
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升级
org.springframework.boot spring-boot-starter-parent 2.7.15 也可以通过设置dependencymanagement的方式: 2.7.15 5.3.29 org.springframework.boot spring-boot-starter-parent ${springboot-version} import pom org.springframework spring-framework-bom ${springframework.version} import pom
循环依赖问题
springboot升级到2.7.15后,如果应用中存在循环依赖的问题,启动时会报如下错误:
原因:官方文档不鼓励循环依赖引用,默认情况下是禁止的
解决方案:
第一种:推荐更新应用中bean的依赖关系来解决 第二种:配置文件中加入以下配置,为了和旧版本保持一致,此配置推荐添加#放开循环依赖spring.main.allow-circular-references=true
6.4、常用中间件升级
6.4.1、lombok版本升级到1.18.20以上
org.projectlombok lombok 1.18.20 如果不升级,编译时会报错如下:
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 解决方案:/** * 增加如下配置可解决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 void customizespringfoxhandlermappings(list mappings) { list copy = mappings.stream().filter(mapping -> mapping.getpatternparser() == null).collect(collectors.tolist()); mappings.clear(); mappings.addall(copy); }@suppresswarnings(unchecked)private list gethandlermappings(object bean) {try { field field = reflectionutils.findfield(bean.getclass(), handlermappings); field.setaccessible(true);return (list) 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)。
解决方案:
手动引入如下包即可
jakarta.xml.bind jakarta.xml.bind-api 2.3.2 org.glassfish.jaxb jaxb-runtime 2.3.2
6.4.4、concrete配置中心阻塞升级
使用 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行)的实现逻辑
解决方案:
在jvm启动参数中设置--add-opens jdk.proxy2来开启私有字段的访问,但因为动态代理生成的包名是随机不明确的,所以这种方案不可行。jdk官方文档也明确表示不支持访问动态代理内部的随机字段。官方说明:https://cr.openjdk.org/~mr/jigsaw/spec/api/java/lang/reflect/proxy.html
代码修改,只需把 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后,项目启动时可能会遇到如下两种类型的异常:
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
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、启动后的验证
推荐先升级jdk11,再到jdk17,一边升级一边进行验证观察
观察日志是否有异常,特别是上面说到的启动时异常
观察监控类软件,比如sgm、ump等监控是否正常
推荐逐步有序切量,并做好常态化压测,防止影响核心业务
升级完成后,最好能做个全流程的功能测试,防止功能异常
七、总结
升级后,除了可以使用新的语法特性,最大的亮点是可以使用亚毫秒级停顿的gc性能(至少百倍的gc性能提升),所以 强烈建议升级到jdk17
整个升级过程并不复杂,主要涉及到中间件版本的升级和启动参数的配置
智能机器人避障安全性概述
有什么热门蓝牙耳机推荐?盘点四款最受欢迎的蓝牙耳机
基于MSP430颅内出血检测设备的研制策略
人机融合成2019年机器人发展的关键趋势
光电液位传感器应用行业有哪些?
JDK11升级JDK17最全实践
LM1875双声道不垫绝缘用散热片可行吗
“产业区块链”这个贴合国家区块链战略发展方向的风口正式成型
Q2季度全球移动应用处理器市场下降26%,高通以29%保持龙头地位
未来几年 人工智能+教育将会持续升级
诺基亚6评测:诺基亚6拍照对比iPhone7 Plus,情理之中意料之外!
栅极驱动器在PFC设计中是干什么用的
smt足迹 几个最容易出问题的地方
Android TV x86 系统发布,让用户在大屏幕PC上正常工作
什么是星基增强系统(SBAS)
中国航展见证珠海创新,一批高精尖“珠海造”产品亮相
万用表如何区分硅二极管与锗二极管
双模组光纤涂覆机是啥?比普通单模光纤涂覆机有哪些优势?
机器人程序中I60该怎样去使用呢
步进电机和伺服电机有什么不同