查看原文
其他

JDK8升级JDK11最全实践干货来了

京东科技 曲振富 京东技术
2024-08-24


Tech

导读

截至目前(2023年),Java8发布至今已有9年,2018年9月25日,Oracle发布了Java11,这是Java8之后的首个LTS版本。那么从JDK8到JDK11,到底带来了哪些特性呢?值得升级吗?而且升级过程会遇到哪些问题呢?带着这些问题,本篇文章将带来完整的JDK8升级JDK11最全实践。




01 为什么升级JDK11

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

1.1  性能提升


    

更好的垃圾收机制、更快的类加载器,加快应用程序的运行速度。综合评估,从Java 8 升级到 Java 11,G1GC平均速度提升16.1%,ParallelGC为4.5%(基于OptaPlanner的用例基准测试表明https://www.optaplanner.org/blog/2019/01/17/HowMuchFasterIsJava11.html)。

1.2  特性和改进


    

局部变类型推断、新的 API、HTTP/2客户端、Lambda表达式的新特性等,这些新特性可以提高开发效率。

1.3  支持最新的技术和框架


    

许多新的技术和框架已经或即将开始依赖于JDK11或以上版本,升级后可以保证应用程序能够分利用这些新的技术和框架。

1.4  长期支持版本


    

JDK11是Oracle官方发布的一个长期支持(LTS),意味着它将获得长期的更新和支持,有助于保持用程序的稳定性和可靠性。

1.5  行业趋势


    

数据来自 New Relic (https://newrelic.com/resources/report/2023-state-of-the-java-ecosystem)在2023年1月发布的Java生态报告,从下图可以看出:

  1. 目前市面上有超过 56%的应用程序使用了JDK 11,Java 8 的使用从2020年的84%降低到了现在的32%左右。大部分公司在这三年之间都升级到了JDK 11 或者 JDK 17这两个LTS版本上面。

  2. 垃圾收集器使用情况来看,JDK11版本及以上G1使用率最高,占比高达65%

图1.Java LTS版本百分比图2.垃圾回收器使用百分比


02   

升级后GC效果

  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
先给出结论:
1、JDK11相对于JDK8,所有垃圾回收器的性能都有提升,特别是大内存机器下G1的提升最明显

2、8G内存以下的机器,推荐使用Parallel GC,如果特别追求低延迟,选择牺牲吞吐量,可以使用G1,并设置期望的最大垃圾回收停顿时间来控制;

3、8G及以上的大内存机器,推荐使用G1 

4、不推荐使用CMS,升级后从各项数据来看,CMS收集器都不如G1。

在JDOS平台上选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用JDK8和JDK11进行部署和压测。

整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512Kb的数据。最终产出不同GC回收器的各项指标数据,来分析GC的性能提升效果。

以下是压测的性能情况:
机器配置垃圾回收器指标项JDK8JDK11JDK11比JDK8提升总结
2C4GParallel GC(标记复制+标记整理)吞吐量88.805%92.821%4%1、JDK11各项指标都有提升 2、当前机器配置下,综合评估,Parallel GC的综合指标比G1高
平均停顿GC时间28.3ms19.6ms30%
最大停顿GC时间720ms720ms0
CMS(标记复制+标记清除)吞吐量58.551%63.923%5%
平均停顿GC时间28.0ms26.5ms7%
最大停顿GC时间300ms250ms16%
G1收集器吞吐量83.046%68.371%-15%
平均停顿GC时间125ms49.9ms60%
最大停顿GC时间1170ms610ms47%
4C8GParallel GC(标记复制+标记整理)吞吐量90.851%95.252%5%1、JDK11各项指标都有明显提升 2、当前机器配置下,综合评估,G1的综合指标比Parallel GC高
平均停顿GC时间27.1ms15.3ms43%
最大停顿GC时间580ms680ms-17%
CMS(标记复制+标记清除)吞吐量49.812%56.55%7%
平均停顿GC时间38.3ms32.3ms15%
最大停顿GC时间180ms150ms16%
G1收集器吞吐量96.333%97.328%1%
平均停顿GC时间18.4ms18.7ms0.01%
最大停顿GC时间980ms190ms80%
8C16GParallel GC(标记复制+标记整理)吞吐量90.114%94.718%4%1、JDK11各项指标都有明显提升 2、当前机器配置,综合评估,大内存机器,G1的综合指标比Parallel GC高很多
平均停顿GC时间30.8ms16.8ms46%
最大停顿GC时间940ms770ms18%
CMS(标记复制+标记清除)吞吐量53.893%60.168%7%
平均停顿GC时间32.2ms27.2ms15%
最大停顿GC时间260ms100ms61%
G1收集器吞吐量96.359%97.143%1%
平均停顿GC时间20.1ms17.3ms14%
最大停顿GC时间260ms120ms53%
* 上面给出的GC升级效果,采用的是默认的配置,没有做任何优化,只提供参考。真正的GC调优是个技术活,需要根据业务需求、机器配置和实际压测效果等综合评估来选出最合适的GC垃圾回收器。

* 不同垃圾回收器的特点:

1.Parallel GC - JDK 8及以下版本的默认收集器,关注吞吐量,尝试在最小延迟的情况下尽快完成工作并提高吞吐量。

2.CMS - 一个老年代收集器,基于标记-清除算法实现,关注延迟,以最短回收停顿时间为目标。

3.Garbage First(G1)- JDK 9以后的默认收集器,G1 关注总体的性能,会尝试在吞吐量和延迟之间做平衡。


03   

JDK11带来了哪些新特性

  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

3.1  GC改进


         
默认垃圾回收器改为G1,废弃CMS垃圾回收器
  • G1特点:目标是降低应用程序的停顿时间并提高吞吐量。

引入ZGC垃圾回收器(可伸缩低延迟垃圾收集器);但由于JDK11中ZGC还不够完善,推荐在JDK17中再使用稳定版ZGC

  • Full GC的停顿不超过10毫秒
  • 支持TB级堆内存回收
  • 相对于G1吞吐量下降不超过15%

3.2  模块化


    

Java9引入了对于模块化软件支持,而Java11进一步扩展了这种特性。模块化让应用程序更精简,减少对其他类库的依赖和冗余代码,提高运行效率和安全性。

然而,目前不推荐使用模块化,因为相关组件生态还不完善,并且模块化带来的价值不够突出。具体原因请看后面章节的详细分析:新特性实践-模块化。

3.3  语法增强


    
  • 局部变量推断,引入var局部变量类型,允许开发人员省略通常不必要的局部变量类型初始化声明

图3.

  • Lambda表达式简化,内部可以使用var

图4.

  • 接口中可以定义私有方法,可以实现接口方法的访问控制和代码复用

图5.

3.4  API增强


    
  • HTTPClient标准化支持:强大而灵活的HTTP客户端API,支持多协议(HTTP/2、WebSocket)、异步非阻塞、流操作和连接池等特性。ps:再也不需要用第三包 HttpClient 工具包

  • 字符串方法增强isBlanklinesstripstripLeadingstripTrailingrepeat

  • Files增强:readString、WriteString

  • InputStream增强:transferTo(流快速拷贝)

  • stream增强,dropWhile(从集合中删除满足的)、takeWhile(从集合中获取满足的)、ofNullable

  • 集合工厂方法:Sets.of()、List.of()、Map.of()、Map.ofEntries(),举例:List<String> list = List.of("Java", "Python", "C++")



04   如何升级  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目

4.1  升级应用评估


    
  • 为保证稳定性,优先在新业务新应用来落地实施JDK11的升级。

4.2  JDK选择


    
自从2019年1月起,Oracle JDK后续的版本开始商用收费,所以推荐大家选择OpenJDK11,OpenJDK和OracleJDK功能上没有差异,支持免费商用。
OpenJDK11下载地址:https://jdk.java.net/archive/

4.3  GC配置


    
根据自身需求和机器配置选择GC,不同GC的JVM启动参数配置:
  • G1垃圾回收器(JDK11默认,不需要手动配置):-XX:+UseG1GC  
  • Parallel GC垃圾回收器:XX:+UseParallelGC

4.4  升级过程踩坑


    
整个升级过程还是比较简单的,除了升级JDK版本,实际遇到的问题如下:
分类依赖名支持情况说明
框架Spring2.X/boot支持

使用JDK11自带原生HttpClient时,会遇到:1、spring启动时,会遇到注入某些类时,无法通过反射的方式访问其所在的包,报错:module java.net.http does not"opens jdk.internal.net.http"to unnamed module @5eb2172

原因:模块化引入了包之间的访问权限控制,如果没有对一个包显示地使用open/opens关键字对外开放,那么其他包中的类无法通过反射的方式访问此包。

解决方案:需要手动设置JVM参数,比如:--add-opens java.net.http/jdk.internal.net.http=ALL-UNNAMED

中间件JSF支持
AKS支持

出现异常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>

Mybatis支持
Concrete支持
R2M支持
EasyJob支持
OSS支持
FMQ支持



监控运维SGM支持
UMP支持
UWC支持
CICDJDOS部署支持JDK11镜像:java-jdt-centos7.4-jdk1.11.0_13-tomcat9.0.54:latest

4.5  升级后验证


    

升级后完成,做好单测和回归测试,推荐能做个压测验证,防止影响线上服务稳定性。



05   新特性实践-模块化  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目

Java一直是构建大型应用程序的主流语言之一。然而随着Java生态系统中存在着大量库和复杂的代码块之间关系难以理清的问题,构建系统变得困难且超出了大家的理解和有效开发的范围。特别是在使用繁多的Java存档文件(Java Archive, JAR)时,这一问题变得更加突出。为了应对这种复杂性,模块化能够很好地管理和减少代码的复杂性。因此自Java9开始,引入了模块化系统。通过模块化,Java本身也得以进行模块化改进。

5.1  模块化是什么


    
模块化指的是JAVA平台的模块系统(Java Platform Module System),简称JPMS。JPMS引入一种新方式来组织和构建Java应用程序,它将代码分为相互独立、可复用的模块。每个块都有自己的命名空间,明确声明并控制其他模块的访问权限。这种模块化设计使得开发人员能够更好地维护复杂的应用程序,提高代码的复用性、可维护性和安全性,同时提升应用的加载速度和性能。最大的特点是可以定义模块描述符来隔离module(Jar包)内部类的访问权限。
模块化的几点关键说明:

1)相对于JDK8的变动

  • JDK9以后引入了一个新组件module:模块描述符module-info.java,用于将一组相关的包放入一个组中。

  • 在Java8和更早的应用程序中,应用程序将包作为顶级组件,Java9以后应用程序将模块作为顶级组件。

  • 一个模块(Jar包)只能有一个module-info.java。

2)和maven的关系

模块化并不是要替代maven,和maven本身并不冲突,maven定义jar之间的依赖关系,模块化是对已经依赖的jar下的包进行更细粒度依赖控制。

3)如何兼容旧应用

天然兼容旧应用。为了向后兼容旧项目,一些库本身并未模块化,其仍然可以作为模块在模块路径中使用,而这些库在模块路径上时会被转化为自动模块,例如:jackson-databind-1.0.0.jar将成为自动模块jackson.databind。

图6、7.

5.2  带来了哪些好处


    
1)封装和隔离,更好的访问控制
模块化允许开发者将代码和资源封装在独立的模块中。模块之间可以明确地定义公开和私有的API,提供了更好的代码隔离性和可维护性。
ps:新业务单应用可以按照领域模型来进行多模块的划分,以避免代码腐化。简单举例单应用下存在产品.jar、订单.jar。订单依赖产品,通过模块化的限制,订单只能使用产品中明确对外暴露的类,这样就避免传统模式订单.jar可能依赖了产品.jar中普通的类导致代码腐化的问题,也降低后续领域服务拆分的复杂度。
2)更好的可伸缩性,加载速度的提升
模块化系统使得Java平台更加可伸缩,通过模块化定义,可以仅加载需要的模块,从而提升加载类的效率,最终减少了应用程序的内存占用和启动时间,同时打包后的程序也更小。
3)明确的依赖关系
模块化系统要求在模块之间明确定义依赖关系。在编译或运行代码之前,模块系统会检查模块是否满足所有依赖关系,从而导致更少的运行时错误。
4)安全
在JVM的最深层次上执行强封装,减少Java运行时的攻击面,同时无法获得对敏感内部类的反射访问。

5.3  如何使用


    
1)定义module-a.jar

包结构如下:

com.jdt.a person Men.java reflect ReflectModel.java module-info.java
module-info文件内容如下:
module module.a { //指令用于指定一个模块中哪些包下的public对外是可访问的,包括直接引入和反射使用 exports com.jdt.a.person; // 只能被反射调用,用于指定某个包下所有的 public 类都只能在运行时可被别的模块进行反射,并且该包下的所有的类及其乘员都可以通过反射进行访问。 opens com.jdt.a.refect; }

2)定义module-b.jar,包的pom中指定依赖了module-a

包结构如下:

com.jdt.b test Test.java module-info.java
module-info文件内容如下:
module module.b { //依赖a下的包 requires module.a;}
3)此时module-b.jar,在编写编码时,会遇到如下问题

图8.

5.4  实践过程的坑


    
上面简单介绍了模块化的知识,具体在落地过程中,主要踩了以下的坑,供大家参考

1)依赖JSF包时无法模块化

* JSF是京东内部使用的高性能RPC框架

进行模块化时,pom中依赖了jsf包,模块定义如下:

module module.a { requires fastjson; //依赖jsf包名 requires jsf.lite;
exports com.jd.jdk.test.module;}
此时编译报错如下:提示找不到模块:jsf.lite,但是pom中明明指定依赖了jsf.lite

图9.

问题原因

经过一系列定位研究,发现jsf-lite包中,/META-INF/services下的文件org.glassfish.jersey.internal.spi.AutoDiscoverable里面写的类是com.alibaba.fastjson.support.jaxrs.FastJsonAutoDiscoverable,此类并未在当前jsf.lite包中定义,属于com.alibaba.fastjson包的。

图10.

但是pom中明明也依赖了com.alibaba.fastjson包,为什么模块化后,就找不到了呢?
主要原因在于模块化遇到SPI(Service Provider Interface)时的约束:模块化时,SPI机制要求配置中定义依赖的类必须本模块定义的,不能是其他模块的包(来自它不拥有的包),否则,此包将无法被模块化。
这样也就解释了,为什么上面jsf无法找到module的问题,jsf-lite里面设置了它不拥有的包:com.alibaba.fastjson.support.jaxrs.FastJsonAutoDiscoverable,导致jsf-lite包无法被自动模块化。

解决方案

联系JSF团队,升级JSF包,修复上面说的FastJsonAutoDiscoverable配置错误的问题。
2)拆包问题(模块隔离)
模块化约束:jdk9以上,使用模块化时不支持拆分包的形式依赖。
拆分包意味着两个模块包含相同的包,Java模块系统不允许拆分包。拆分包始终是不正常的,而当使用解析可传递依赖项的构建工具(如Maven等)时,很容易出现同一个库的多个版本,当Java模块系统检测到一个包存在于模块路径上的多个模块中时,就会拒绝启动。

例如

module-a.jar包结构定义:com.foo.package A.java
module-b.jar包结构定义:com.foo.package B.java

当module-c同时依赖module-a和module-b时,如上编译时会报一个错,Package com.foo.package in both module module.b and module module.a,这就是JAVA9的模块隔离,要求只能从一个模块(module)中读取同一个包(package),不能跨模块读取。

解决方案

如果在使用模块化时,遇到了拆分包问题,无论如何都是无法绕过的。即使从用户角度来看基于类路径的应用程序可以正确工作,你也最终需要处理这些问题。此时只能停用模块化或升级jar包,避免拆分包问题

5.5  模块化落地总结


    

目前不推荐使用模块化,因为相关组件生态还不完善,并且模块化带来的价值不够突出:

1.很多中间件都是基于jdk8构建的,都有可能遇到模块化兼容的问题,比如:jsf,需要jsf强制升级才可以使用模块化;

2.拆包问题无法解决,比如:aws-java-sdk-s3、fluent等。



06   总结  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
1、升级过程简单,升级后可以使用更多新特性和更好的GC性能,所以建议升级到JDK11。

2、现阶段不推荐使用模块化,但是不用担心会影响JDK11的升级。

另外听说JDK17的 ZGC可以达到亚秒级停顿,但考虑到JDK11的ZGC还不是很稳定,所以本次不做测试,后面升级到JDK17后有机会再给大家分享ZGC压测效果。

希望以上分享可以给大家带来实际的帮助。



推荐阅读618技术揭秘:大促弹窗搭投实践
ChatGPT的探索与实践
当小白遇到FullGC
618技术大揭秘:Switchquery秒级配置触达平台的设计与实现

求分享

求点赞

求在看

打造SAAS化服务的会员徽章体系,可以作为标准的产品化方案统一对外输出。结合现有平台的通用能力,实现会员行为全路径覆盖,并能结合企业自身业务特点,规划相应的会员精准营销活动,提升会员忠诚度和业务的持续增长。底层能力:维护用户基础数据、行为数据建模、用户画像分析、精准营销策略的制定

▪功能支撑:会员成长体系、等级计算策略、权益体系、营销底层能力支持

▪用户活跃:会员关怀、用户触达、活跃活动、业务线交叉获客、拉新促活

继续滑动看下一个
京东技术
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存