天河区门户网站教育局,wordpress 内容注入,做平面设计素材的哪个网站好,现在哪些行业需要建设网站前言
自 2014 年发布以来#xff0c; JDK 8 一直都是相当热门的 JDK 版本。其原因就是对底层数据结构、JVM 性能以及开发体验做了重大升级#xff0c;得到了开发人员的认可。但距离 JDK 8 发布已经过去了 9 年#xff0c;那么这 9 年的时间#xff0c;JDK 做了哪些升级 JDK 8 一直都是相当热门的 JDK 版本。其原因就是对底层数据结构、JVM 性能以及开发体验做了重大升级得到了开发人员的认可。但距离 JDK 8 发布已经过去了 9 年那么这 9 年的时间JDK 做了哪些升级是否有新的重大特性值得我们尝试能否解决一些我们现在苦恼的问题带着这份疑问我们进行了 JDK 版本的调研与尝试。
新特性一览
现如今的 JDK 发布节奏变快每次新出一个版本我们就会感叹一下我还在用 JDK 8现在都 JDK 9、10、11 …… 21 了然后就会瞅瞅又多了哪些新特性。有一些新特性很香但考虑一番还是决定放弃升级。主要原因除了新增特性对我们来说改变不大以外最重要的就是 JDK 9 带来的模块化JEP 200导致我们升级十分困难。
模块化的本意是将 JDK 划分为一组模块这些模块可以在编译时、构建时和运行时组合成各种配置主要目标是使实现更容易扩展到小型设备提高安全性和可维护性并提高应用程序性能。但付出的代价非常大最直观的影响就是一些 JDK 内部类不能访问了。
但是除此之外并没有太多阻塞升级的问题后续版本都是一些很香的特性
G1 JEP 248、JEP 307、JEP 344、JEP 345、JEP 346提供一个支持指定暂停时间、NUMA 感知内存分配的高性能垃圾回收器ZGC JEP 333、JEP 376、JEP 377一个支持 NUMA暂停时间不应超过 1ms 的垃圾回收器并发 API 更新JEP 266提供 publish-subscribe 框架支持响应式流发布 - 订阅框架的接口以及 CompletableFuture 的进一步完善集合工厂方法JEP 269类似 Guava支持快速创建有初始元素的集合新版 HTTP 客户端JEP 321一个现代化、支持异步、WebSocket、响应式流的 JDK 内置 API空指针 NPE 直接给出异常方法位置JEP 358以前只给代码行数不告诉哪个方法一行多个方法的写法一但出现空指针全靠程序员上下文分析推理instanceof 的模式匹配JEP 394判断类型后再也不用强转了数据记录类JEP 395一个标准的值聚合类帮助程序员专注于对不可变数据进行建模实现数据驱动Switch 表达式语法改进JEP 361改变 Switch 又臭又长易于出错的现状文本块JEP 378支持二维文本块而不是像现在一样通过 号自行拼接密封类JEP 409提供一种限制进行扩展的语法超类应该可以被广泛访问因为它代表了用户的重要抽象但不能广泛扩展因为它的子类应该仅限于作者已知的子类以及一些未提到的底层数据结构优化JVM 性能提升……
这么多的优点恰好能解决我们当前遇到的一些问题因此我们决定进行 JDK 升级。
升级
升级应用评估
首先自然是要考虑要将哪些应用进行升级。我们根据以下条件进行应用筛选
第一也是最重要的一点此系统可以通过升级解决现有问题与瓶颈第二有完备的机制能够进行快速回归与验证如完备的单元测试自动化测试覆盖能力便捷的生产压测能力等底层的升级一定要做好完备的验证第三技术债务一定要少不至于在升级过程中遇到一些必须解决的技术债给升级增加难度第四负责升级的人对这个系统都很了解除核心业务逻辑外还能够了解引入了哪些中间件与依赖使用了中间件的哪些功能中间件升级后大量不兼容的改动是否对现有系统造成影响
最终我们选取了一个结算页、收银台展示无券支付营销的应用进行升级。此应用特点如下
作为核心链路的应用之一接口响应时间要求很高GC 是其耗时抖动的瓶颈之一业务正在进行快速迭代发展随着降本增效策略的落地营销策略进一步精细化营销种类、数量、范围进一步增加给系统性能带来更大的挑战日常流量不低整点存在突发流量并且需要承接大促流量核心链路覆盖了单元测试测试环境具备自动化回归能力预发、生产支持常态化压测与生产流量回放非 Web 应用仅使用各个中间件的基础功能升级出现不兼容的问题小维护了 3 年经历过多次重构历史问题较少几乎没有技术债务
针对以上特点此应用很适合进行 JDK 17 升级。此应用基于 JDK 8SpringBoot 2.0.8除常见外部基础组件外还使用以下公司内部中间件UMP、SGM、DUCC、CDS、JMQ、JSF、R2M。
升级效果
可以先看下我们升级后压测的效果
纯计算代码不再受 GC 影响 升级前 升级后 版本吞吐量平均耗时最大耗时JDK 8 G199.966%35.7ms120msJDK 17 ZGC99.999%0.0254ms0.106ms
升级后吞吐量几乎不受影响甚至提升0.01%GC 平均耗时下降1405 倍GC 最大耗时下降1132 倍
升级步骤
升级 JDK 编译版本
首先自然是修改 maven 中指定的 JDK 版本可以先升级到 JDK 11同时修改 maven 编译插件
java.version11/java.version
maven-compiler-plugin.version3.8.1/maven-compiler-plugin.version
maven-source-plugin.version3.2.1/maven-source-plugin.version
maven-javadoc-plugin.version3.3.2/maven-javadoc-plugin.version
maven-surefire-plugin.version2.22.2/maven-surefire-plugin.versionplugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version${maven-compiler-plugin.version}/version configuration release${java.version}/release encoding${project.build.sourceEncoding}/encoding /configuration
/plugin
引入缺少的依赖
然后就可以进行本地编译了此时会暴露一些很简单的问题比如找不到包、类等等。原因就是 JDK 11 移除了 Java EE and CORBA 的模块需要手动引入。
!-- JAVAX --
dependency groupIdjavax.annotation/groupId artifactIdjavax.annotation-api/artifactId version1.3.1/version
/dependency
dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.0/version
/dependency
dependency groupIdcom.sun.xml.bind/groupId artifactIdjaxb-impl/artifactId version2.3.0/version
/dependency
dependency groupIdcom.sun.xml.bind/groupId artifactIdjaxb-core/artifactId version2.3.0/version
/dependency
dependency groupIdjavax.activation/groupId artifactIdactivation/artifactId version1.0.2/version
/dependency
升级外部中间件
解决了编译找不到类的问题接下来就该升级依赖的外部中间件了。对于我们的应用来说也就是升级 SpringBoot 的版本。支持 JDK 17 的版本是 Spring 5.3对应 SpringBoot 2.5。
在这里我建议升级至 SpringBoot 2.7从 2.5 升级至 2.7 几乎没有需要改动的地方同时高版本的 SprngBoot 所约定的依赖对 JDK 17 的支持也更好。
建议进行大版本逐个升级比如我们从 2.0 升级至 2.1。每升一个版本就要仔细观察依赖版本的变化掌握每个依赖升级的情况。SpringBoot 的升级其实意味着把所有开源组件约定版本进行大版本升级接口弃用破坏性兼容更新较多需要一一鉴别。
下面以升级 Spring Boot 2.1 为例说明我们升级的步骤 首先阅读 Spring Boot 2.1 做了哪些和我们有关的配置改动 禁用了同 Bean 覆盖开启需要指定spring.main.allow-bean-definition-overriding为true 然后阅读 Spring Boot 2.1 升级了哪些我们用到的依赖 Spring 升级至 5.1 首先阅读 Spring 5.1 做了哪些和我们有关的配置改动 无影响 然后阅读 Spring 5.1 升级了哪些我们用到的依赖 ASM 7.0 同理阅读升级影响这种底层依赖的底层依赖如果仅 ASM 在使用则无需关心 CGLIB 3.2 同理阅读升级影响这种底层依赖的底层依赖如果仅 ASM 在使用则无需关心 最后阅读 Spring 5.1 弃用了哪些和我们有关的配置与依赖 无影响 Lombok 升级至 1.18 阅读改动影响1.18 Lombok 默认情况下将不再生成私有无参构造函数。可以通过在 lombok.config 配置文件中设置 lombok.noArgsConstructor.extraPrivatetrue 来启用它 Hibernate 升级至 5.3 阅读改动影响对我们项目无影响 JUnit 升级至 5.2 阅读改动影响需要 Surefire 插件升级至 2.21.0及以上 最后阅读 Spring Boot 2.1 弃用了哪些和我们有关的配置与依赖
至此Spring Boot 2.1 升级完毕。接下来分析一次依赖树变化和升级前的依赖树进行比较查看依赖变化范围是否全部已知可控。完成后进行 Spring Boot 2.2 的升级。
以下为我们需要注意的升级事项仅供参考 可以先升级到 JDK 11一边启动一边验证。但不要在 JDK 11 使用 ZGCZGC 的堆预留与可用堆的比例太大有时会导致 OOM 代码中存在同 Bean启动时 Springboot 2.0 会自动进行覆盖高版本开启覆盖需要指定spring.main.allow-bean-definition-overriding为true Spring Boot 2.2 默认的单元测试 Junit 升级至 5Junit 4 的单元测试建议进行升级改动不大 Spring Boot 2.4 不再支持 Junit 4 的单元测试如果需要可以手动引入 Vintage 引擎 Spring Boot 2.4 配置文件处理逻辑变更注意阅读更新日志 Spring Boot 2.6 默认禁用 Bean 循环依赖可以通过将 spring.main.allow-circular-references 设置为 true开启 Spring Boot 2.7 自动配置注册文件变更spring.factories中的内容需要移动至META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件下 spring-boot-properties-migrator可以识别弃用的属性可以考虑使用 Spring Framework 5.2 需要 Jackson 2.9.7注意阅读更新日志 Spring Framework 5.2 注解检索算法重构所有自定义注释都必须使用 Retention(RetentionPolicy.RUNTIME) 进行注释以便 Spring 能够找到它们 Spring Framework 5.3 修改了很多东西但都与我们的应用无关请关注更新日志 ASM 仅单元测试 Mock 在使用无需特殊关注做好 JUnit 升级兼容即可 CGLIB 大版本升级以兼容字节码版本为主关注好变更日志即可 Lombok 即使是小版本升级也会有破坏性更新需要仔细阅读每个版本的更新日志建议少用 Lombok Hibernate 没有太大的破坏性更新关注好变更日志即可 JUnit 升级主要关注大版本变更如 4 升 5小版本没有特别大的破坏性更新并且是单元测试使用的依赖可以放心升级或者不升级 Jackson 2.11对 java.util.Date 和 java.util.Calendar 默认格式进行了更改注意查看更新日志进行兼容 注意字节码增强相关依赖的升级 注意本地缓存升级 注意 Netty 升级关注更新日志
升级内部中间件
内部中间件升级较为简单主要是关注 JMQ、JSF 版本。其中 JSF 依赖的 Netty 和 Javassist 等都需要升级Netty 版本较低会有内存泄漏问题。
我们使用的依赖版本
给大家参考下我们升级后的依赖版本
properties !-- 基础组件版本 Start -- java.version17/java.version project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven-compiler-plugin.version3.11.0/maven-compiler-plugin.version maven-surefire-plugin.version2.22.2/maven-surefire-plugin.version jacoco-maven-plugin-version0.8.10/jacoco-maven-plugin-version maven-assembly-plugin-version2.4.1/maven-assembly-plugin-version maven-dependency-plugin-version3.1.0/maven-dependency-plugin-version profiles.dirsrc/main/profiles/profiles.dir springboot-version2.7.13/springboot-version log4j2.version2.18.0-jdsec.rc2/log4j2.version hibernate-validator.version5.2.4.Final/hibernate-validator.version collections-version3.2.2/collections-version collections4.version4.4/collections4.version netty.old.version3.9.0.Final/netty.old.version netty.version4.1.36.Final/netty.version javassist-version3.29.2-GA/javassist-version guava.version23.0/guava.version mysql-connector-java.version5.1.29/mysql-connector-java.version jmh-version1.36/jmh-version caffeine-version3.1.6/caffeine-version fastjson-version1.2.83-jdsec.rc1/fastjson-version fastjson2-version2.0.35/fastjson2-version roaringBitmap.version0.9.44/roaringBitmap.version disruptor.version3.4.4/disruptor.version jaxb-impl.version2.3.8/jaxb-impl.version jaxb-core.version2.3.0.1/jaxb-core.version activation.version1.1.1/activation.version !-- 基础组件版本 End -- !-- 京东中间件版本 Start -- ump-version20221231.1/ump-version ducc.version1.0.20/ducc.version jdcds-driver-alg-version2.21.1/jdcds-driver-alg-version jdcds-driver-version3.8.3/jdcds-driver-version jmq.version2.3.3-RC2/jmq.version jsf.version1.7.6-HOTFIX-T2/jsf.version r2m.version3.3.4/r2m.version !-- 京东中间件版本 End -- /properties
JVM 启动参数升级
远程 DEBUG 参数有所变化
JAVA_DEBUG_OPTS -agentlib:jdwptransportdt_socket,servery,suspendn,address*:8000
打印 GC 日志参数的变化我们在预发环境开启了日志进行观察
JAVA_GC_LOG_OPTS -Xlog:gc*:file/export/logs/gc.log:time,tid,tags:filecount10:filesize10m
使用了 ZGC 的部分 JVM 参数
JAVA_MEM_OPTS -server -Xmx12g -Xms12g -XX:MaxMetaspaceSize256m -XX:MetaspaceSize256m -XX:MaxDirectMemorySize2048m -XX:UseZGC -XX:ZAllocationSpikeTolerance3 -XX:ParallelGCThreads8 -XX:CICompilerCount3 -XX:-RestrictContended -XX:AlwaysPreTouch -XX:ExplicitGCInvokesConcurrent -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/export/logs
内部依赖需要访问 JDK 模块如 UMP、JSF、虫洞、MyBatis、DUCC、R2M、SGM
if [[ $JAVA_VERSION -ge 11 ]]; then SGM_OPTS${SGM_OPTS} --add-opens jdk.management/com.sun.management.internalALL-UNNAMED --add-opens java.management/sun.managementALL-UNNAMED --add-opens java.management/java.lang.managementALL-UNNAMED UMP_OPT --add-opens java.base/sun.net.utilALL-UNNAMED JSF_OPTS --add-opens java.base/sun.util.calendarALL-UNNAMED --add-opens java.base/java.utilALL-UNNAMED --add-opens java.base/java.mathALL-UNNAMED WORMHOLE_OPT --add-opens java.base/sun.security.actionALL-UNNAMED MB_OPTS --add-opens java.base/java.langALL-UNNAMED DUC_OPT --add-opens java.base/java.netALL-UNNAMED R2M_OPT --add-opens java.base/java.timeALL-UNNAMED
fi
启动后完整的启动参数如下
-javaagent:/export/package/sgm-probe-java/sgm-probe-5.9.5-product/sgm-agent-5.9.5.jar -Dsgm.server.addresshttp://sgm.jdfin.local -Dsgm.app.namemarket-reduction-center -Dsgm.agent.sink.http.connection.requestTimeout2000 -Dsgm.agent.sink.http.connection.connectTimeout2000 -Dsgm.agent.sink.http.minAlive1 -Dsgm.agent.virgo.address10.24.216.198:8999,10.223.182.52:8999,10.25.217.95:8999 -Dsgm.agent.zonem6 -Dsgm.agent.groupm6-discount -Dsgm.agent.tenantjdjr -Dsgm.deployment.platformjdt-jdos --add-opensjdk.management/com.sun.management.internalALL-UNNAMED --add-opensjava.management/sun.managementALL-UNNAMED --add-opensjava.management/java.lang.managementALL-UNNAMED -DJDOS_DATACENTERJXQ -Ddeploy.app.namejdos_kj_market-reduction-center -Ddeploy.app.id30005051 -Ddeploy.instance.id0 -Ddeploy.instance.nameserver -Djava.awt.headlesstrue -Djava.net.preferIPv4Stacktrue -Djava.util.Arrays.useLegacyMergeSorttrue -Dog4j2.contextSelectororg.apache.logging.log4j.core.async.AsyncLoggerContextSelector -Dlog4j2.AsyncQueueFullPolicyDiscard -Xmx12g -Xms12g -XX:MaxMetaspaceSize256m -XX:MetaspaceSize256m -XX:MaxDirectMemorySize2048m -XX:UseZGC -XX:ZAllocationSpikeTolerance3 -XX:ParallelGCThreads8 -XX:CICompilerCount3 -XX:-RestrictContended -XX:AlwaysPreTouch -XX:ExplicitGCInvokesConcurrent -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/export/logs --add-opensjava.base/sun.net.utilALL-UNNAMED --add-opensjava.base/sun.util.calendarALL-UNNAMED --add-opensjava.base/java.utilALL-UNNAMED --add-opensjava.base/java.mathALL-UNNAMED --add-opensjava.base/sun.security.actionALL-UNNAMED --add-opensjava.base/java.langALL-UNNAMED --add-opensjava.base/java.netALL-UNNAMED --add-opensjava.base/java.timeALL-UNNAMED -Dloader.path/export/package/jdos_kj_market-reduction-center/conf
系统验证
系统可以成功启动后就可以进行功能验证。有几个验证重点与方法 首先可以通过单元测试快速进行系统全面回归避免出现 JDK API、中间件 API 变更导致的业务异常 部署到测试环境验证各个中间件是否正常如 DUCC 开关下发MQ 收发JSF 接口调用等等系统中所有用到的中间件都需要一一验证 然后可以开始进行核心业务的验证这时候可以利用测试同学的测试自动化能力加人工补充场景快速进行核心业务回归。其中研发需要观察系统被调用时的所有异常日志包括警告明确每条日志产生的原因 验证完成后可以部署到联调环境利用外部同事联调时的请求进一步进行验证 充分在测试环境观察后部署至预发环境利用外部同事联调时的请求进一步进行验证并进行常态化压测验证优化效果与瓶颈 经过预发长时间验证没有问题后部署一台生产通过回放生产流量进一步进行验证 回放流量无异常后开始承接生产流量按接口开量进行若干周的观察 逐步切量直到全量上线
GC 调优
ZGC 介绍 如图所示ZGC 的定位是一个最大暂停时间小于 1ms且能够处理大小从 8MB 到 16TB 的堆并且易于调优的垃圾回收器。ZGC 只有三个 STW 阶段具体流程网上有大量类似文章这里不做详细介绍。
优化方向
目前我们的应用日常使用 G1 约 30ms 的 GC 停顿时间不到 1 分钟就会触发一次大促时频率更高暂停时间更长导致接口性能波动较大。随着业务发展为了优化系统我们大量应用了本地缓存导致存活对象较多。ZGC 暂停时间不随堆、活动集或根集大小而增加且极低的 GC 时间正是我们需要的特性因此决定使用 ZGC。
ZGC 作为一个现代化 GC没有必要做过多的优化默认配置已经可以解决 99.9% 的场景。但是我们的应用会承接大促流量根据观察瞬时流量激增时 GC 时机较晚因此应对突发流量是我们 ZGC 调优的一个目标其他属性不做任何调整。
优化措施
ZGC 的一个优化措施就是足够大的堆一般来说给 ZGC 的内存越多越好但我们也没必要浪费通过压测观察 GC 日志取得一个合适的值即可。我们只要保证 堆可以容纳应用程序产生的实时垃圾 堆中有足够的空间以便在 GC 运行时为新的垃圾分配提供空间
因此我们将机器升级成 8C 16G 配置观察 GC 日志根据应用情况调整内存占用配置最终设定为-Xmx12g -Xms12g -XX:MaxMetaspaceSize256m -XX:MetaspaceSize256m -XX:MaxDirectMemorySize2048m提升 ZGC 的效果。
剩下的其他优化措施则视情况而定可以调整触发 GC 的时机也可以改为基于固定时间间隔触发 GC。
我们略微提升了触发时机-XX:ZAllocationSpikeTolerance3默认为 2应对突发流量。
CICompilerCount ParallelGCThreads一个是提升 JIT 编译速度一个是垃圾收集器并行阶段使用的线程数根据实际情况略微增加牺牲一点点 CPU 使用率提升下效率。
另外还可以开启Large Pages进一步提升性能。这一步我们没有做因为现在部署方式为一台物理机 Docker 混部署。开启需要修改内核影响宿主机的其他镜像。
总结
至此调优完成目前我们已在线上跑了一个多月每周都有三次常态化压测一切正常。
以上升级心得分享给大家希望对各位有所帮助。 作者京东科技 张天赐 来源京东云开发者社区