51Testing软件测试论坛

标题: 从JDK8升级到JDK11,看这篇就足够了 [打印本页]

作者: lsekfe    时间: 2020-7-7 14:25
标题: 从JDK8升级到JDK11,看这篇就足够了
一些背景在背景知识,我们会讨论一些关于新的JDK Release周期,OpenJDK特性归一化,LTS(Long-term support长期支持版本)的事情。
1. 新的发布周期这个就可以长话短说了,反正我们知道如下两点就好:
2. OpenJDK已可以作为新的线上标准JDK在2018.9之前,Oracle JDK是大家普遍运用于线上的JDK,OpenJDK的特性并不完全,并且Oracle JDK号称做了很多优化。在2018.9之后,Oracle JDK正式商用(开发不收费,但是运行线上业务收费)。但是与此同时,Oracle宣布,OpenJDK与Oracle JDK在功能上不会有区别。并且,OpenJDK 11 RTS将会由红帽社区进行维护。这样,更加增加了可靠性与保证问题的及时解决。
我们可以在线上使用OpenJDK,开发时,使用任意的JDK。
3. LTS(Long-term support长期维护)版本对于商业版的JDK,不同的厂商都将长期维护版本定在JDK 11/17/23/...
对于OpenJDK,社区说,对于这些版本,至少会提供四年的维护更新时间。每个长期维护版本都会有一个固定的管理者,对于OpenJDK11,应该就是红帽社区。现在源代码搞定了,但是,我们应该从哪里获取编译好的OpenJDK呢?这个可以交给AdoptOpenJDK,它会一直收集不同版本的OpenJDK以及全平台的build好的OpenJDK
4. Amazon CorrettoAWS也提供了自己的OpenJDK,Amazon Corretto:
OpenJDK社区的FAQ部分曾经提到:“Amazon从2017年开始贡献OpenJDK,并且计划开始大量贡献”。我猜Amazon会把他们在Corretto上面做的优化,合并到OpenJDK源码中,即使没有,Corretto也是开源的,迟早会有人参考并在OpenJDK源码上进行修改。同时也说明,OpenJDK的更新也会及时被合并到Corretto中。
准备迁移1. 更新好开发环境以及编译环境各种常用工具,建议升级到如下版本以后:
对于如下工具,由于已经不再维护,需要替换成其他工具:
同时由于在Java 9 之后,每六个月bytecode level会提升一次。如果你依赖的库有处理字节码相关的库,应该注意下版本升级,例如:
2. 引入JPMS后,相关的迁移工作2.1. Java EE相关模块默认不在Java包里面了,相关的类需要增加额外依赖或者替换成其他的类如果你的项目中使用了这些类,那么在编译阶段就会报错,例如:
  1. error: package javax.xml.bind does not exist
  2. import javax.xml.bind.JAXBException;
复制代码

如果你是用JDK 8编译成功,拿到JDK 11运行,就会报错:
  1. Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
  2.     at monitor.Main.main(Main.java:27)
  3. Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
  4.     at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
  5.     at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
  6.     at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
  7.     ... 1 more
复制代码


以下是相关移除列表还有解决方案
  1. <dependency>
  2.     <groupId>com.sun.activation</groupId>
  3.     <artifactId>javax.activation</artifactId>
  4.     <version>1.2.0</version>
  5. </dependency>
复制代码


  1. <dependency>
  2.     <groupId>javax.transaction</groupId>
  3.     <artifactId>javax.transaction-api</artifactId>
  4.     <version>1.2</version>
  5. </dependency>
复制代码

  1. <dependency>
  2.     <groupId>javax.xml.bind</groupId>
  3.     <artifactId>jaxb-api</artifactId>
  4.     <version>2.2.8</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>com.sun.xml.bind</groupId>
  8.     <artifactId>jaxb-core</artifactId>
  9.     <version>2.2.8</version>
  10. </dependency>
  11. <dependency>
  12.     <groupId>com.sun.xml.bind</groupId>
  13.     <artifactId>jaxb-impl</artifactId>
  14.     <version>2.2.8</version>
  15. </dependency>
复制代码

  1. <dependency>
  2.     <groupId>com.sun.xml.ws</groupId>
  3.     <artifactId>jaxws-ri</artifactId>
  4.     <version>2.3.0</version>
  5.     <type>pom</type>
  6. </dependency>
复制代码


  1. <dependency>
  2.     <groupId>javax.annotation</groupId>
  3.     <artifactId>javax.annotation-api</artifactId>
  4.     <version>1.3.1</version>
  5. </dependency>
复制代码


一个建议就是,在你的项目中如果没有冲突,建议都加上这些依赖。
2.2. 模块可见性导致的内部API不能调用的问题这个在我另一篇文章也说过:https://zhanghaoxin.blog.csdn.net/article/details/90514045
在Java9之后引入了模块化的概念,是将类型和资源封装在模块中,并仅导出其他模块要访问其公共类型的软件包。如果模块中的软件包未导出或打开,则表示模块的设计人员无意在模块外部使用这些软件包。 这样的包可能会被修改或甚至从模块中删除,无需任何通知。 如果仍然使用这些软件包通过使用命令行选项导出或打开它们,可能会面临破坏应用程序的风险!
对于这种限制,在编译阶段,可能会有类似下面的报错:
  1. error: package com.sun.imageio.plugins.jpeg is not visible
  2. import com.sun.imageio.plugins.jpeg.JPEG;
  3.                               ^
  4.   (package com.sun.imageio.plugins.jpeg is declared
  5.   in module java.desktop, which does not export it)
复制代码
如果是反射的调用,可能在运行阶段有类似于如下的报警:
  1. WARNING: An illegal reflective access operation has occurred
  2. WARNING: Illegal reflective access by j9ms.internal.JPEG
  3.     (file:...) to field com.sun.imageio.plugins.jpeg.JPEG.TEM
  4. WARNING: Please consider reporting this
  5.     to the maintainers of j9ms.internal.JPEG
  6. WARNING: Use --illegal-access=warn to enable warnings
  7.     of further illegal reflective access operations
  8. WARNING: All illegal access operations will be denied in a future release
  9. # here's the reflective access to the static field com.sun.imageio.plugins.jpeg.JPEG.TEM
复制代码
对于这种错误,我们最好是更换API,如果难以实现,则可以通过添加编译以及启动参数解决。
我们需要的参数是:
  1. --add-exports <source-module>/<package>=<target-module-list>
复制代码

如果设置target-module-list为ALL-UNNAMED,那么所有Classpath下的module,都可以访问source-module中的pakage包下的公共API
  1. --add-opens <source-module>/<package>=<target-module-list>
复制代码
如果设置target-module-list为ALL-UNNAMED,那么所有Classpath下的module,都可以访问source-module中的pakage包下的所有成员类型
对于编译阶段,也就是javac命令,我们只需要添加--add-exports,对于上面的例子,就是:
  1. javac --add-exports java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED
复制代码
对于运行阶段,也就是java命令,我们最好把--add-exports和--add-open都加上,对于上面的例子,就是:
  1. java --add-exports java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED --add-open java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED
复制代码


这样,在运行阶段,首先不会有禁止访问报错,同时也不会有警告。
同时,为了在运行期能找到所有需要添加的模块和包,可以通过添加--illegal-access=${value}来检查。这个value可以填写:
我们可以设置--illegal-access=deny来知道我们需要添加的所有--add-export和--add-open包。
2.3 通过JDK11内置jdeps工具查找过期以及废弃API以及对应的替换这个也在我另一篇文章提到过:
https://zhanghaoxin.blog.csdn.net/article/details/100732605
  1. jdeps --jdk-internals -R --class-path 'libs/*' $project
复制代码
libs是你的所有依赖的目录,$project是你的项目jar包,示例输出:
  1. ... JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 [url=home.php?mod=space&uid=295386]@since[/url] 1.8 sun.reflect.Reflection Use java.lang.StackWalker
复制代码
@since 9
在这里简单提一些在JDK11过期,但是JDK8使用的API:
2.4. ClassLoader变化带来的URLClassLoader的变化Java 8的ClassLoader流程:
java9及之后的classloader流程:
同时,我们注意到,JDK9开始,AppClassLoader他爹不再是 URLClassLoader
一般热部署,插件部署,都会使用到AppClassLoader,例如Spring-Boot的热部署,老版本的会报异常:
  1. Exception in thread "main" java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader at org.springframework.boot.devtools.restart.DefaultRestartInitializer.getUrls(DefaultRestartInitializer.java:93) at org.springframework.boot.devtools.restart.DefaultRestartInitializer.getInitialUrls(DefaultRestartInitializer.java:56) at org.springframework.boot.devtools.restart.Restarter.<init>(Restarter.java:140) at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:546) at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationStartingEvent(RestartApplicationListener.java:67) at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationEvent(RestartApplicationListener.java:45) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:122) at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:69) at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:48) at org.springframework.boot.SpringApplication.run(SpringApplication.java:292) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) at com.asofdate.AsofdateMain.main(AsofdateMain.java:18)
复制代码


这是主要是因为AppClassLoader不再是URLClassLoader的子类导致的。
之前对于动态加载的类,我们总是通过将这个类通过反射调用URLClassLoader加到classpath里面进行加载。这么加载在JDK11中已经无法实现,并且这样加载的类不能卸载。
对于动态加载的类,我们在OpenJDK11中只能自定义类加载器去加载,而不是通过获取APPClassLoader去加载。同时,这么做也有助于你随时能将动态加载的类卸载,因为并没有加载到APPClassLoader。
建议使用自定义的类加载器继承SecureClassLoader去加载类:
java.security.SecureClassLoader
最后,如果你想访问classpath下的内容,你可以读取环境变量:
  1. String pathSeparator = System .getProperty("path.separator"); String[] classPathEntries = System .getProperty("java.class.path") .split(pathSeparator);
复制代码
2.5. 过期启动参数修改
JDK 8 到JDK 11有很多参数变化,可以总结为两类参数的变化,一是GC相关的(GC配置调优更加简单),二是日志相关的,日志统一到了一起,不像之前那么混乱
具体请参考:
每个说明参考三部分:
3. 一些框架的OpenJDK11兼容问题持续收集(持续更新中)


作者:张哈希
链接:https://www.jianshu.com/p/778e1e6d0cda
来源:简书


作者: 千里    时间: 2020-8-1 08:41
现在还是建议升级为8+版本




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2