背景
目前大部分java运行的环境还是JDK8为主,JDK8是2014年发布的了,而且8u201/202 之后版本,oracle不在提供免费商用支持,JDK17于2021年09月14日发布,是最新的官方LTS(Long term support 长期支持),且商用免费,从相关基准测试来看,相比JDK8都有10%左右的提升,而且提供了新的低延迟ZGC,对G1也进行了很大的提升,这些特性,都让人想进行升级。
本文基于spring boot 2.4 ,工程集成了MySQL5.7,mybatis plus,lettuce(redis 驱动) ,这些都是典型工程依赖,进行升级JDK17,看看期间会遇到那些坑。
升级步骤
一:下载安装JDK17
直接从oracle官方下载,注意OS和CPU版本 oracle JDK17
装好后,需要配置JDK17的环境变量,这里由于大家工作中还是以JDK8为主,所以需要在开发环境工作保持2套JDK环境变量,这里作者的开发环境是mac os,一个类Linux系统。window和Linux开发环境的同学,可以提供一个思路,参考我的思路进行配置。
打开bash 的source文件,进行配置:
# maven bin所在的目录
export M2_HOME=/Users/xxxx/Documents/dev_tool/apache-maven-3.5.2
# 将maven bin加到PATH变量中
export PATH=$PATH:$M2_HOME/bin
# 配置JAVA_HOME所在的目录,注意这个目录下应该有bin文件夹,bin下应该有java等命令
export JAVA_8_HOME=/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home
export JAVA_11_HOME=/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home 08 export JAVA_17_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
alias jdk8="export JAVA_HOME=$JAVA_8_HOME" #编辑一个命令jdk8,输入则转至jdk1.8
alias jdk11="export JAVA_HOME=$JAVA_11_HOME" #编辑一个命令jdk11,输入则转至jdk11
alias jdk17="export JAVA_HOME=$JAVA_17_HOME"
export JAVA_HOME=$JAVA_8_HOME
# MySQL config
export MYSQL_HOME=/usr/local/mysql
# add mysql config
export PATH=$PATH:$MYSQL_HOME/bin
# add gradle config
export GRD_HOME=/Users/xxxx/Documents/dev_tool/gradle-2.7
# add path
export PATH=$PATH:$GRD_HOME/bin
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
备注:由于本人使用的事mac book m1芯片,所以是去zulu zuluJDK 下载了m1编译的版本,会比oracle的版本快一些,如果同学没有这个情况,还是推荐去oracle官网下载
这样,可以在命令窗口快速切换JDK
如果是idea,可以在IDE进行JDK配置,这里不在赘述。
二:使用JDK17重新编译运行
重新编译这个没啥好说,有了上面的环境变量快速切换,就可以切到jdk17,查看maven版本,就可以看到jdk环境,当然,如何你不像重新编译,理论也是可以运行的,这里我进行重新打包编译,工程代码没有做任何修改
重新编译后,直接运行,发现报错
1 解决模块报错
这个是由于 JDK9 之后,引入模块化导致的。 启动命令加上 :--add-opens java.base/java.lang=ALL-UNNAMED 即可,
如果是ide 需要在启动配置加上vm参数 (但是本次测试 需要打jar包启动,ide启动是散包)
java --add-opens java.base/java.lang=ALL-UNNAMED -jar monitordemo-1.0.0.jar 可以顺利启动
这里的模块化,没有很好的解决版本,需要根据自己工程中那些项目进行反射来决定,在现有的大量三方包,框架,还有一些自己组内的平台代码,都大量使用了反射,AOP技术,这里在JDK9 的模块化技术引入后,默认都是禁止的,需要在JVM启动参数进行声明,这里可以给一个我在多个项目实际运行的声明参数,大家可以加入自己的启动脚本,基本可以使用大部分场景
--add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED
2 SSL协议问题
升级JDK17后,MySQL链接失败,出现如下报错:
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
原因是在JDK8_u292版本就移除了对 TLS1.0支持(见官方文档),所以在HTTPS,或者别的需要用到TLS加密请求的地方都会报错 ,JDK17是移除了,所以需要进行JRE配置修改:
在{JAVA_HOME}路径 /jdk-17.0.1/conf/security/java.security
找到
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
删除SSLv3, TLSv1, TLSv1.1, 改为 jdk.tls.disabledAlgorithms=RC4, DES, MD5withRSA, \
可以用vi 进行搜索修改
修改后,重启即可
三:体验和测试对比
测试环境
mac os ,m1芯片,JDK 使用zulu-jdk-17,arm64版本
测试代码
比较简单:new 一千万对象,并且排序
1:使用G1
JDK17 默认就是G1 垃圾回收器,不用加额外参数
这个是默认的G1 GC ,从jstat -gcutil {pid} 1000 1000监控命令可以看出 ,还没有FGC ,大约每次YGC暂停1~400ms(默认参数)
使用命令行启动 30秒预热 ,new 对象 花了2.9秒,排序花了 0.9~1.2秒
CPU 波动较大,70~320%
2:使用ZGC
启动默认是G1 GC,如果要使用ZGC,需要在启动命令加入 -XX:+UseZGC 对于刚才的模块声明,启动命令就变成:
java --add-opens java.base/java.lang=ALL-UNNAMED -XX:+UseZGC -jar monitordemo-1.0.0.jar
jstat -gcutil {pid} 1000 1000 命令可以看到,ZGC 确实做到 <1ms 暂停,只有一个O区,没有分代
new对象花了2.5秒,排序花了 3.8~4.4秒,比G1 要差,这就是说的ZGC 吞吐会下降
CPU 约200%
3:使用JDK8 G1 编译运行
为了和JDK8对比,这里用JDK8 进行编译运行对比,由于JDK8没有ZGC,这里只在G1下进行对比
启动命令: java -XX:+UseG1GC -jar monitordemo-1.0.0.jar 表现不错,排序 1.3秒 new 对象 2.5秒
G1 gc 会有3~600ms 以内的暂停 ,该值可以进行设置,但是这里都使用默认值,所以不进行设置
4:简单结论
升级JDK17 可以带来更全面性能提升,G1的吞吐比ZGC要好。ZGC 可以做到<1ms 暂停
JDK17-G1 |
JDK-ZGC |
JDK8-G1 |
|
排序一千万对象 |
0.9~1.2s | 3.8~4.4s | 1.1~1.4s |
new一千万对 |
2.6~3.3s 平均 2.9s |
2.4~2.9s 平均 2.6s |
2.5~2.9s 平均2.8s |
CPU | 100~300% | 100~200% | 100~400% |
YGC 时间 | 100~400ms | <1ms | 100~600ms |
FGC暂停时间 | 无 | 无 | 无 |