
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
—
对于本次升级来说,有JDK11、JDK17两个版本可供选择。
从长远来看,JDK17更代表未来,但是目前JDK11使用更加广泛,相关的升级指南和文档更加丰富,因此升级到JDK11比升级到JDK17的挑战要相对小,并且基于风险的控制,从JDK8升级到JDK11,随后再从JDK11升级到JDK17是一个更合理的选择。
因此本次升级选择JDK11,同时相应的Tomcat 7升级到Tomcat 8.5。
03
—
JDK11版本特性
引入ZGC[(可伸缩,低延迟的gc)
相关JEP 333: ZGC: A Scalable Low-Latency Garbage Collector(Experimental)
【https://openjdk.org/jeps/333】
-
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】
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 容器改进
移除内容
| 删除的模块 | 受影响的包 | 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包的顺序不同
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 8.5通过WebappClassLoaderBase.start()加载WEB-INF/lib的jar包,然后DirResourceSet.class中获取文件列表直接进行加载,并没有进行排序,从而导致在不同的操作系统中返回的结果不一致(Windows系统按照文件名称排序,Linux系统乱序)。源码如下:

导致的问题
-
项目中存在两个相同的类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 -
。。。。
-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、系统日志都需查看下是否有异常 -
重要业务发布线上时一定要谨慎,必须进行全面测试
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/JAXBExceptionat 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.JAXBExceptionat 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)
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.ExceptionInInitializerErrorat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1163)Caused by: java.lang.NullPointerException: nullat 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
<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.StackOverflowErrorat 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)
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跨域问题异常如下所所示:
07
—
展望
作者 | 孙景亮 资深服务端开发工程师

