随着springboot3的正式版推出,最小可用的jdk版本为17。最大的亮点是颠覆性的zgc,可以将stw时间控制在亚毫秒级别,接着jdk21推出了分代zgc。根据jdk官方说法,此gc算法适用于任何内存条件下,多核cpu环境。我们也在各个环境的生产系统做到了验证(2c4g、4c8g、8c16g)。对于中间件,rocketmq、kafka、es、nacos等。因所用jdk版本尚未完全支持,且目前也没有遇到过问题,暂未做升级。
对于新项目来说,springboot3+jdk21已经可用,基本上不会有问题。
对于springboot2+jdk8项目的升级,也相对较简单。
升级步骤#
1、jdk我们选择zulu21 https://www.azul.com/downloads/#zulu ↗
2、pom配置统一到21
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>xml3、从2.7.x升级springboot到最新版本,根据官方的Migration Guide升级即可 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide ↗
4、循环依赖,某些项目目前碰到了此类情况。修改配置即可
spring.main.allow-circular-references=trueproperties5、lombok版本升级到最新
6、找不到相关的类,一般是因为从jdk11开始删除了j2ee相关的类。根据报错引入jakarta相关包即可,代码中将j2ee相关代码,改为使用jakarta
7、某些应用,会使用到反射。新jdk版本中因为安全问题,做了一些限制。目前做得比较粗暴,在jvm中增加如下配置
--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMEDshell8、swagger改为使用springdoc-openapi 依赖,并做相应的代码修改
9、docker打包基础镜像修改为统一使用zulu21
升级后,所有生产环境jvm配置统一修改#
以下是用得最多的4c8g配置
-Xmx6g -Xms6g #堆内存初始化6g
-XX:+UseZGC #使用zgc
-XX:-ZUncommit #关闭将不使用的内存返回给操作系统
-XX:CICompilerCount=3 #4核cpuJIT编译线程数 https://help.aliyun.com/document_detail/2856899.html?spm=a2c4g.11186623.help-menu-2584271.d_2_16_6.1ab423085DdeTz&scm=20140722.H_2856899._.OR_help-T_cn#DAS#zh-V_1
-XX:+UseDynamicNumberOfGCThreads #当开启时,ZGC 会根据GC运行状态(例如GC耗时、堆空余空间、对象分配频率等)由内置的启发式算法自动选择并发阶段的 GC 线程数量(最小为 1,最大为 -XX:ConcGCThreads)。当关闭时,则会固定使用 -XX:ConcGCThreads 数量的线程。
-XX:+ZGenerational #启用jdk21生产可用的分代zgc算法,会在gc的时候消耗更多的cpu
-XX:+AlwaysPreTouch #启用内存预分配,需要内核版本4.3+
#gc日志记录到/opt/logs/文件名为启动的时间戳,每个日志记录包含时间戳、日志级别、线程id、标签。文件大小限制到50M,达到后创建新的文件继续记录
-Xlog:gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M
#发生OOM时dump内存日志
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/apps/errorDump.hprofshell因为目前公司的java服务均跑在容器内部,Huge Pages、NUMA Support等未做开启
总结#
本文探讨了如何升级到jdk21,并使用最新的分代zgc。jdk21也带来了很多简便的新语法和适合IO密集的虚拟线程。虚拟线程相比于平台线程在某些场景下优势明显。只是需要注意仅使用可重入锁对象,而不使用synchronize关键字(反而降低性能)。相关资料:https://openjdk.org/jeps/444 ↗
经过实践和测试,我们发现升级后的系统在垃圾回收方面表现出色,暂停时间确实如官方所说被控制到了亚毫秒内。尽管这个优化会额外消耗cpu资源,但获得了超低stw时间,权衡这下显然非常值得。
相比其他已经经过生产环境调优的垃圾回收器(G1、Parallel、CMS),ZGC的性能和稳定性都非常优秀,几乎不需要太多额外的调优,就可以得到响应时间的降低和吞吐量的增加。即使在以前吞吐量有优势的Parallel,依然具有优势。
ZGC可以统一江湖了!另据得到的消息,某些大厂主要服务均升级到了jdk21。但也进一步说明了,(分代)zgc和虚拟线程的强大。你们还在用jdk8吗?
更新#
新增一些中大厂使用jdk17/jdk21和zgc的案例,作为以后项目的参考
References#
Spring-Boot-3.0-Migration-Guide ↗:迁移到springboot3
转转 ↗:“分代ZGC在转转商列服务中的实践”
得物 ↗:“亚毫秒GC暂停到底有多香?JDK17+ZGC初体验|得物技术”
京东 ↗:“JDK11升级JDK17最全实践干货来了”
京东营销 ↗:“JDK 17 营销初体验 —— 亚毫秒停顿 ZGC 落地实践”
美团 ↗: “新一代垃圾回收器ZGC的探索与实践”
阿里云 ↗:“The Past and Present of JDK8 and JDK17 to the Future of JDK21”
Automq ↗:Java ZGC 深度剖析及其在构建低延迟流系统中的实践心得