原文地址:https://blog.codefx.org/java/java-11-migration-guide/。 在原文的基础上,增长了一些我遇到的具体的坑还有在特定场景下的解决方案,供你们参考html
在背景知识,咱们会讨论一些关于新的JDK Release周期,OpenJDK特性归一化,LTS(Long-term support长期支持版本)的事情。java
这个就能够长话短说了,反正咱们知道以下两点就好:linux
在OpenJDK 11以前,Oracle JDK是你们广泛运用于线上的JDK,OpenJDK的特性并不彻底,而且Oracle JDK号称作了不少优化。在OpenJDK 11以后,Oracle JDK正式商用(开发不收费,可是运行线上业务收费)。可是与此同时,Oracle宣布,OpenJDK与Oracle JDK在功能上不会有区别。而且,OpenJDK 11 RTS将会由红帽社区进行维护。这样,更加增长了可靠性与保证问题的及时解决。spring
咱们能够在线上使用OpenJDK,开发时,使用任意的JDK。bootstrap
对于商业版的JDK,不一样的厂商都将长期维护版本定在JDK 11/17/23/...api
对于OpenJDK,社区说,对于这些版本,至少会提供四年的维护更新时间。每一个长期维护版本都会有一个固定的管理者,对于OpenJDK11,应该就是红帽社区。如今源代码搞定了,可是,咱们应该从哪里获取编译好的OpenJDK呢?这个能够交给AdoptOpenJDK,它会一直收集不一样版本的OpenJDK以及全平台的build好的OpenJDK安全
AWS也提供了本身的OpenJDK,Amazon Corretto:oracle
OpenJDK社区的FAQ部分曾经提到:“Amazon从2017年开始贡献OpenJDK,而且计划开始大量贡献”。我猜Amazon会把他们在Corretto上面作的优化,合并到OpenJDK源码中,即便没有,Corretto也是开源的,早晚会有人参考并在OpenJDK源码上进行修改。同时也说明,OpenJDK的更新也会及时被合并到Corretto中。app
各类经常使用工具,建议升级到以下版本之后:框架
对于以下工具,因为已经再也不维护,须要替换成其余工具:
同时因为在Java 9 以后,每六个月bytecode level会提高一次。若是你依赖的库有处理字节码相关的库,应该注意下版本升级,例如:
若是你的项目中使用了这些类,那么在编译阶段就会报错,例如:
error: package javax.xml.bind does not exist import javax.xml.bind.JAXBException; ^
若是你是用JDK 8编译成功,拿到JDK 11运行,就会报错:
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException at monitor.Main.main(Main.java:27) Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496) ... 1 more
如下是相关移除列表还有解决方案
<dependency> <groupId>com.sun.activation</groupId> <artifactId>javax.activation</artifactId> <version>1.2.0</version> </dependency>
<dependency> <groupId>javax.transaction</groupId> <artifactId>javax.transaction-api</artifactId> <version>1.2</version> </dependency>
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.8</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.8</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.8</version> </dependency>
<dependency> <groupId>com.sun.xml.ws</groupId> <artifactId>jaxws-ri</artifactId> <version>2.3.0</version> <type>pom</type> </dependency>
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.1</version> </dependency>
一个建议就是,在你的项目中若是没有冲突,建议都加上这些依赖。
这个在我另外一篇文章也说过:https://zhanghaoxin.blog.csdn.net/article/details/90514045
在Java9以后引入了模块化的概念,是将类型和资源封装在模块中,并仅导出其余模块要访问其公共类型的软件包。若是模块中的软件包未导出或打开,则表示模块的设计人员无心在模块外部使用这些软件包。 这样的包可能会被修改或甚至从模块中删除,无需任何通知。 若是仍然使用这些软件包经过使用命令行选项导出或打开它们,可能会面临破坏应用程序的风险!
对于这种限制,在编译阶段,可能会有相似下面的报错:
error: package com.sun.imageio.plugins.jpeg is not visible import com.sun.imageio.plugins.jpeg.JPEG; ^ (package com.sun.imageio.plugins.jpeg is declared in module java.desktop, which does not export it)
若是是反射的调用,可能在运行阶段有相似于以下的报警:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by j9ms.internal.JPEG (file:...) to field com.sun.imageio.plugins.jpeg.JPEG.TEM WARNING: Please consider reporting this to the maintainers of j9ms.internal.JPEG WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release # here's the reflective access to the static field com.sun.imageio.plugins.jpeg.JPEG.TEM
对于这种错误,咱们最好是更换API,若是难以实现,则能够经过添加编译以及启动参数解决。
咱们须要的参数是:
--add-exports
选项:模块声明中的exports语句将模块中的包导出到全部或其余模块,所以这些模块可使用该包中的公共API。 若是程序包未由模块导出,则可使用-add-exports
的命令行选项导出程序包:--add-exports <source-module>/<package>=<target-module-list>
若是设置target-module-list为ALL-UNNAMED,那么全部Classpath下的module,均可以访问source-module中的pakage包下的公共API
--add-opens
选项: 模块声明中的opens语句使模块里面的包对其余模块开放,所以这些模块能够在运行期使用深层反射访问该程序包中的全部成员类型。 若是一个模块的包未打开,可使用--add-opens命令行选项打开它。 其语法以下:--add-opens <source-module>/<package>=<target-module-list>
若是设置target-module-list为ALL-UNNAMED,那么全部Classpath下的module,均可以访问source-module中的pakage包下的全部成员类型
对于编译阶段,也就是javac命令,咱们只须要添加--add-exports
,对于上面的例子,就是:
javac --add-exports java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED
对于运行阶段,也就是java命令,咱们最好把--add-exports
和--add-open
都加上,对于上面的例子,就是:
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
包。
这个也在我另外一篇文章提到过: https://zhanghaoxin.blog.csdn.net/article/details/100732605
jdeps --jdk-internals -R --class-path 'libs/*' $project
libs是你的全部依赖的目录,$project是你的项目jar包,示例输出:
... JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8 sun.reflect.Reflection Use java.lang.StackWalker @since 9
在这里简单提一些在JDK11过时,可是JDK8使用的API:
Java 8的ClassLoader流程:
java9及以后的classloader流程:
同时,咱们注意到,JDK9开始,AppClassLoader他爹再也不是 URLClassLoader
通常热部署,插件部署,都会使用到AppClassLoader
,例如Spring-Boot的热部署,老版本的会报异常:
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下的内容,你能够读取环境变量:
String pathSeparator = System .getProperty("path.separator"); String[] classPathEntries = System .getProperty("java.class.path") .split(pathSeparator);
JDK 8 到JDK 11有不少参数变化,能够总结为两类参数的变化,一是GC相关的(GC配置调优更加简单),二是日志相关的,日志统一到了一块儿,不像以前那么混乱
具体请参考:
每一个说明参考三部分: