大数跨境
0
0

JDK升级总结

JDK升级总结 微鲤技术团队
2023-06-06
1
导读:本文介绍了JDK11升级步骤和问题总结。






01


背景



 项目升级JDK11,主要基于以下几个出发点:

  • 目前公司使用的JDK8,在2019年1月已经停止更新维护

  • 越来越多的框架和第三方库新版本不再兼容低版本JDK,如果想使用新版本的特性就必须升级。以下是一些常见的框架和第三方库,在其最新版本中需要使用JDK11及以上版本:
    • Spring Framework 5.3及以上版本
    • Hibernate ORM 5.4及以上版本
    • Apache Tomcat 10.0及以上版本
    • Jetty 10.0及以上版本
    • Apache POI 5.0及以上版本
    • Apache Lucene 8.7及以上版本
    • JUnit 5.7及以上版本
  • JDK21即将在2023年9月发布,距离JDK8已经相差了10多个版本,这些版本不断引入的新特性对于提高服务的启动、性能和内存使用情况将会有很多帮助

因此JDK升级是一个必要的过程,即使不是为了获取新特性,也应该考虑升级以获得最新的安全更新和性能优化。





02


版本选择


JDK发布周期通常是每6个月发布一个新版本(非LTS),而每个三年左右发布一个LTS(Long-term support)版本。LTS版本提供了长期的支持和维护,企业通常会优先选择LTS版本进行开发和部署。LTS版本受到官方支持和维护,因此可以得到长期的安全更新和错误修复。相比之下,非LTS版本只获得较短时间的支持,在短时间内很可能会被弃用。
目前JDK的四个LTS版本中主要以JDK8、JDK11为主,如下图所示:

对于本次升级来说,有JDK11、JDK17两个版本可供选择。

从长远来看,JDK17更代表未来,但是目前JDK11使用更加广泛,相关的升级指南和文档更加丰富,因此升级到JDK11比升级到JDK17的挑战要相对小,并且基于风险的控制,从JDK8升级到JDK11,随后再从JDK11升级到JDK17是一个更合理的选择。

因此本次升级选择JDK11,同时相应的Tomcat 7升级到Tomcat 8.5。





03


JDK11版本特性


升级前了解JDK11版本特性的必要性是非常重要的。不同版本之间会有新功能,架构变化、安全修复和性能优化等方面的不同特性。如果不了解这些新特性,就容易在应用程序开发和部署过程中遇到问题。以下是JDK11的新特性:

引入ZGC[(可伸缩,低延迟的gc)

相关JEP 333: ZGC: A Scalable Low-Latency Garbage Collector(Experimental)

https://openjdk.org/jeps/333

JDK11引入了ZGC(实验性质,不建议用到生产环境)。ZGC 即 Z Garbage Collector(垃圾收集器或垃圾回收器),这应该是 Java 11 中最为瞩目的特性,没有之一。
ZGC 是一个可伸缩的、低延迟的垃圾收集器,主要为了满足如下目标进行设计:
  • GC 停顿时间不超过 10ms
  • 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
  • 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
  • 方便在此基础上引入新的 GC 特性和利用 colord
  • 针以及 Load barriers 优化奠定基础
  • 当前只支持 Linux/x64 位平台

HTTP Client API(正式特性)

相关JEP 323: Local-Variable Syntax for Lambda Parameters【https://openjdk.java.net/jeps/323】

对Http Client API 进行了标准化,并且现在完全支持异步非阻塞,该 API 通过 CompleteableFutures 提供非阻塞请求和响应语义,可以联合使用以触发相应的动作。新 Http Client API,提供了对 HTTP/2 等业界前沿标准的支持,同时也向下兼容 HTTP/1.1,精简而又友好的 API 接口,与主流开源 API(如:Apache HttpClient、Jetty、OkHttp 等)类似甚至拥有更高的性能。
    HttpClient client = HttpClient.newHttpClient();    HttpRequest request = HttpRequest.newBuilder()          .uri(URI.create("http://openjdk.java.net/"))          .build();    client.sendAsync(request, BodyHandlers.ofString())          .thenApply(HttpResponse::body)          .thenAccept(System.out::println)          .join();

Docker 容器改进

在JDK10之前,Docker容器中运行Java应用程序一直存在一个问题:容器中运行JVM程序在设置内存大小和CPU使用率后,会导致应用程序的性能下降,这是因为JVM无法识别在容器上设置的内存和CPU约束。
从JDK10开始,JVM会使用容器控制组 (cgroups) 设置的约束来设置内存和CPU限制。另外还添加了“JVM 选项”,使Docker容器用户可以精细地控制用于Java堆的系统内存量。

移除内容

这些Java EE 或 CORBA 模块中的包在JDK9弃用,在JDK11中删除。因此升级过程中,项目中使用到则需要手动引入
删除的模块 受影响的包 maven依赖
Java API for XML Web Services (JAX-WS) java.xml.ws https://mvnrepository.com/artifact/com.sun.xml.ws/jaxws-rt
用于 XML 绑定的 Java 体系结构 (JAXB) java.xml.bind https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime
JavaBeans Activation Framework (JAV) java.activation https://mvnrepository.com/artifact/javax.activation/activation
常见注解 java.xml.ws.annotation https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api
通用对象请求代理体系结构 (CORBA) java.corba https://mvnrepository.com/artifact/org.glassfish.corba/glassfish-corba-orb
Java 事务 API (JTA) java.transaction https://mvnrepository.com/artifact/javax.transaction/jta




04


Tomcat版本区别

Tomcat 7和Tomcat 8.5之间存在以下几个区别:

  • Servlet规范:Tomcat 7支持Servlet 3.0规范,而Tomcat 8.5支持Servlet 3.1规范。
  • WebSocket支持:Tomcat 8.5对WebSocket的支持更好,Tomcat 7对WebSocket支持较弱。
  • JSP规范:Tomcat 7支持JSP 2.2规范,而Tomcat 8.5支持JSP 2.3规范。
  • 远程配置:Tomcat 8.5引入了远程配置API,允许在运行时动态修改Tomcat的配置文件,而Tomcat 7没有这个功能
  • 对多线程的支持:Tomcat 8.5对高并发的支持更好,能够处理更多的请求,而Tomcat 7在高并发时可能会出现性能瓶颈。
  • 加载jar包的顺序不同
其中,加载jar包的顺序会直接影响现有项目是否可以正常启动、运行。

Tomcat加载jar

Tomcat加载jar顺序、以及对应类加载器如下所示:
  • Bootstrap ClassLoader类加载器:加载$java_home/lib 目录下的java核心api
  • Extension ClassLoader类加载器:加载$java_home/lib/ext 目录下的java扩展jar包
  • Application ClassLoader类加载器:加载java -classpath/-Djava.class.path所指的目录下的类与jar包
  • Common ClassLoader类加载器:$CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载
  • Catalina ClassLoader类加载器:$CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载
  • Shared ClassLoader类加载器:$CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载
  • Web App1 ClassLoader类加载器:项目路径/WEB-INF/classes下的class文件
  • Web App2 ClassLoader类加载器:项目路径/WEB-INF/lib下的jar文件


Tomcat 7通过WebappLoader.class加载WEB-INF/lib的jar包,然后在FileDirContext.class中获取文件列表后中按照文件名称首字母a-z进行排序后加载,源码如下:

Tomcat 8.5通过WebappClassLoaderBase.start()加载WEB-INF/lib的jar包,然后DirResourceSet.class中获取文件列表直接进行加载,并没有进行排序,从而导致在不同的操作系统中返回的结果不一致(Windows系统按照文件名称排序,Linux系统乱序)。源码如下:

导致的问题

由于加载jar包的顺序不同,可能会导致无法加载到原定的类。如图所示
  • 项目中存在两个相同的类LogbackServletContainerInitializer.class(packge相同,jar包不同),项目当前加载的是a-suishen-patch-libs中的
  • 在Tomcat 7中可以通过设置jar包的名称保证a-suishen-patch-libs中的先加载
  • 但是在Tomcat 8.5就无法保证加载顺序,最终导致项目无法启动

解决方式

Tomcat 8.5在StandardRoot中的list方法指定了加载资源的顺序,源码如下:

因此,我们可以通过将需要先加载的jar设置在PreResources里,确保优先加载。

设置方式:在项目的META-INF/context.xml中配置

<?xml version="1.0" encoding="UTF-8"?><Context>    <Resources>        <PreResources base="${catalina.base}/webapps/${project}/WEB-INF/lib/a-suishen-patch-libs-1.0-SNAPSHOT.jar"                      className="org.apache.catalina.webresources.JarResourceSet"                      webAppMount="/WEB-INF/classes"/>    </Resources></Context>





05


升级步骤

步骤一:代码修改
  • JDK升级代码修改:可参考https://learn.microsoft.com/zh-cn/java/openjdk/transition-from-java-8-to-java-11

  • jar包记载顺序设置
  • 第三方依赖升级(按需):
    • javassist升级至3.23.1-GA
    • ASM升级至7.0
    • Byte Buddy升级至1.9.0
    • cglib升级至3.2.8
    • Mokito升级至2.20.0
    • 。。。。
步骤二:设置JVM参数
-Xms3000m -Xmx3000m -XX:+UseCompressedOops -XX:+PrintGCDetails -Xloggc:./logs/gc.log -XX:+TieredCompilation -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:MaxTenuringThreshold=15 -XX:+AlwaysPreTouch

步骤三:项目发布

发布过程中,观察cataliana、localhost、项目日志是否有异常,项目是否启动成功。
升级注意事项:
  • cataliana、localhost、系统日志都需查看下是否有异常
  • 重要业务发布线上时一定要谨慎,必须进行全面测试





06


升级问题总结


以下是项目升级过程中遇到的问题以及对应的解决办法。

模块移除一:NoClassDefFoundError

异常如下所示:

Caused by: java.lang.NoClassDefFoundError: javafx/util/Pairat java.base/java.lang.Class.getDeclaredMethods0(Native Method)at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3166)at java.base/java.lang.Class.getDeclaredMethods(Class.java:2309)at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:613)at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:524)at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:510)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:247)… 31 moreCaused by: java.lang.ClassNotFoundException: javafx.util.Pairat org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1420)at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1228)… 38 more

原因:JDK11移除了javafx

解决方式:手动引入依赖

<dependency>    <groupId>org.openjfx</groupId>    <artifactId>javafx-controls</artifactId>    <version>11</version></dependency><dependency>    <groupId>org.openjfx</groupId>    <artifactId>javafx-fxml</artifactId>    <version>11</version></dependency>

模块移除二:NoClassDefFoundError

异常如下所示:

[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1080)Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)

原因:Java 11 删除了 java.xml.bind (JAXB)

解决方式:手动引入依赖

<dependency>    <groupId>javax.xml.bind</groupId>    <artifactId>jaxb-api</artifactId>    <version>2.3.0</version></dependency><dependency>    <groupId>com.sun.xml.bind</groupId>    <artifactId>jaxb-core</artifactId>    <version>2.3.0</version></dependency><dependency>    <groupId>com.sun.xml.bind</groupId>    <artifactId>jaxb-impl</artifactId>    <version>2.3.0</version></dependency>

jar包冲突 NoSuchMethodError

异常如下所示:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authorityMenuServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException:Error creating bean with name 'roleMenuServiceImpl': Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.apache.commons.beanutils.BeanUtilsBean.<init>(Lorg/apache/commons/beanutils/ConvertUtilsBean;)Vat org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1272)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
原因:具有相同packge的BeanUtilsBean.class在多个jar中出现,Tomcat8.5加载了项目不需要的类,导致无法找到对应的方法
解决方式:通过查看依赖树,确定具体的冲突依赖,排除不需要的jar
mvn dependency:tree./gradlew app:dependencies

javassist依赖版本不匹配

异常如下所示:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hessianAppService': Instantiation of bean failed; nested exception is java.lang.ExceptionInInitializerError        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1163)Caused by: java.lang.NullPointerException: null        at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:103)        at javassist.util.proxy.DefineClassHelper.toClass3(DefineClassHelper.java:151)        at javassist.util.proxy.DefineClassHelper.toClass2(DefineClassHelper.java:134)        at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:95)        at javassist.ClassPool.toClass(ClassPool.java:1143)        at javassist.CtClass.toClass(CtClass.java:1316)        at com.alibaba.dubbo.common.compiler.support.JavassistCompiler.doCompile(JavassistCompiler.java:123)        at com.alibaba.dubbo.common.compiler.support.AbstractCompiler.compile(AbstractCompiler.java:59)        at com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler.compile(AdaptiveCompiler.java:46)        at
原因:引入的javassist的版本(3.21.0-GA)和JDK11不匹配
解决方式:javassist升级到3.23.1-GA版本
<dependency>    <groupId>org.javassist</groupId>    <artifactId>javassist</artifactId>    <version>3.21.0-GA</version></dependency>

log4j循环依赖配

异常如下所示:
 org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/admin]]at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:440)at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:198)at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:753)at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)at java.base/java.lang.Thread.run(Thread.java:834)Caused by: java.lang.StackOverflowError   at org.apache.log4j.Category.<init>(Category.java:55)   at org.apache.log4j.Logger.<init>(Logger.java:37)   at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:43)   at org.apache.log4j.LogManager.getLogger(LogManager.java:45)   at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)
原因:log4j-over-slf4j.jar 和 slf4j-log4j12.jar、或jcl-over-slf4j.jar和slf4j-jcl.jar循环依赖导致溢出
解决方式:需要排除slf4j-log4j12、或者slf4j-jcl.jar
参考文档:
https://blog.csdn.net/qq_19749625/article/details/126893801
https://www.slf4j.org/codes.html#log4jDelegationLoop

跨域问题

异常如下所示:
原因:log4j-over-slf4j.jar 和 slf4j-log4j12.jar、或jcl-over-slf4j.jar和slf4j-jcl.jar循环依赖导致溢出
解决方式:需要排除slf4j-log4j12、或者slf4j-jcl.jar
参考文档:https://blog.csdn.net/qq_19749625/article/details/126893801https://www.slf4j.org/codes.html#log4jDelegationLoop
跨域问题异常如下所所示:
原因:tomcat高版本在处理跨域问题时,当allowedOrigins=[*]时,不允许配置supportsCredentials=[true]
解决方式:配置supportsCredentials=[false]或者删除、或者考虑使用”allowedOriginPatterns”代替





07


展望


在项目升级到JDK11后,后续希望能更充分利用JDK11、Tomcat8,5生态带来的便利性,探索启动优化、开发便捷性带来的性能提升。



作者 | 孙景亮 资深服务端开发工程师

本文来自微鲤技术团队,转载请注明出处。


【声明】内容源于网络
0
0
微鲤技术团队
践行数据驱动理念,相信技术改变世界。
内容 25
粉丝 0
微鲤技术团队 践行数据驱动理念,相信技术改变世界。
总阅读76
粉丝0
内容25