目前主要有Oracle JDK,OpenJDK和其他一些企业编译的JDK。
简单地说,OpenJDK是OracleJDK的开源版本。
以下OpenJDK的介绍来自维基百科:
OpenJDK原是Sun Microsystems公司为Java平台构建的Java开发环境(JDK)的开源版本,完全自由,开放源码。Sun Microsystems公司在2006年的JavaOne大会上称将对Java开放源代码,于2009年4月15日正式发布OpenJDK。甲骨文在2010年收购Sun Microsystem之后接管了这个项目。 历史: 2008年5月,Fedora 9及Ubuntu 8.04于发行版中发布OpenJDK,完整地基于自由及开放源代码的OpenJDK。 2008年6月,IcedTea 6(Fedora 9上的一个包版本的OpenJDK)宣布已通过Technology Compatibility Kit测试,可以称得上是一个完全兼容的Java 6的运行环境。 2008年7月12日,Debian接受了OpenJDK-6的不稳定版本,但当前情况已经稳定。OpenJDK也可以在openSUSE、Red Hat Enterprise Linux及其派生系统,如CentOS中找到。 自2008年7月,OpenJDK 7可以运行在Mac OS X和其他的BSD发行版。 2009年7月,Ubuntu 9.04中的二进制版本OpenJDK在Java SE 6 JCK中通过了所有的兼容性测试。 2016年8月22日,Google在Android 7.0 Nougat中,将专利的JDK替换成开源方案的OpenJDK,以彻底解决Java的专利问题。
Oracle JDK之前被称为SUN JDK,甲骨文收购SUN之后改名为Oracle JDK。实际上,Oracle JDK也是基于OpenJDK源代码构建的,因此Oracle JDK和OpenJDK之间并没有重大的技术差异,基本上可以认为性能、功能和执行逻辑上两者是一致的。
两者的不同之处主要有:
关于OpenJDK,还有一些事情需要了解。
目前Java的开发是以OpenJDK项目的形式进行的,而OpenJDK项目是完全开源的(许可证是 GPLv2+CE,就是说你可以根据OpenJDK的源码开发自己的JDK,但是你的JDK也得要开源),该项目目前由Oracle主导,汇聚了社区的力量进行开发,IBM,红帽等企业都有参与。在OpenJDK项目的官网上你可以看到OpenJDK的源码(包括JVM)与提交记录等。
该项目只提供源码,并没有提供各个操作平台(linux,windows,mac等)上的JDK binaries。
Oracle's OpenJDK与AdoptOpenJDK都是对OpenJDK项目的源码进行build之后的产物,即JDK binaries。这两者只要版本相同就是一样的,且两者均提供了不同操作系统(linux,windows,mac等)的binaries。但AdoptOpenJDK会提供更长(可能长达数年)的更新服务,而Oracle's OpenJDK只提供六个月的更新服务。因此建议使用AdoptOpenJDK,而不是Oracle's OpenJDK。
另外,Oracle's OpenJDK不是Oracle JDK。Oracle JDK是Oracle提供的付费版本的JDK,和Oracle's OpenJDK几乎没有区别,但Oracle JDK提供长时间的支持服务、安全更新等。
事实上也可以直接对OpenJDK的源码进行build,来生成自己的JDK binaries,然后自己追踪OpenJDK的源码更新,自己打安全补丁。
对于那些对安全性稳定性要求很高,同时比较富裕的企业来说,也不一定要使用Oracle JDK。其他的许多公司,比如阿里巴巴,亚马逊,IBM等,也提供了自己版本的JDK binaries,他们或者也免费,或者有着不同的收费、更新周期等,可以根据企业自己的实际需要来进行选择。
例如Amazon Corretto,也是一个基于OpenJDK编译的,采用GPL+CE协议开源的JDK。
从2017年9月发布Java 9开始,Oracle每六个月就会发布一个新版本的JDK(具体来说是每年的三月和九月),每三年会有一个LTS(Long Term Support release,长期支持)版本。Java 8 与 Java 11 为当前提供支持的LTS版本。下一个LTS版本应该是Java 17。
下图来自wiki-Java版本历史: https://zh.wikipedia.org/zh-cn/Java%E7%89%88%E6%9C%AC%E6%AD%B7%E5%8F%B2
Oracle JDK对LTS版本提供三年支持,而社区的AdoptOpenJDK承诺对LTS版本提供至少4年的支持。
我们建议的策略是:
AdoptOpenJDK下载网址: https://adoptopenjdk.net/
这个地址从国内访问太慢了,可以从清华大学提供的镜像网站下载:
国内镜像: https://mirrors.tuna.tsinghua.edu.cn/AdoptOpenJDK/
这里下载的是 OpenJDK11U-jdk_x64_linux_hotspot_11.0.7_10.tar.gz
。
AdoptOpenJDK目前可以选择的JVM有两种。一种是原有的hotspot,另一种是IBM开源的openj9。
目前一个简单的评价是,openj9占用的内存更少,但CPU密集任务方面不如hotspot。
就目前而言,一般开发与生产环境还是推荐hotspot,其理论、JVM参数、命令行工具及性能调优的资料更丰富一些。
如果想切换到openj9,需要对其JVM的相关理论、参数、工具进行一些必要的调研。
找个目录存放下载好的openJDK11,如: /usr/java
;
解压缩:
tar -zxvf OpenJDK11U-jdk_x64_linux_hotspot_11.0.7_10.tar.gz
然后设置环境变量(编辑 /etc/profile
):
export JAVVA_HOME=/usr/java/jdk-11.0.7+10 export CLASSPATH=.:${JAVA_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH
然后执行 source /etc/profile
,并确认java版本: java -version
。
https://zhuanlan.zhihu.com/p/87157172
。 当然,Java版本升级是有风险的。例如从Java8升级到Java11的话,因为从Java9开始,JDK中去除了一些模块,如JAXB和JAX-WS的相关依赖,对于这些去除的模块,如果你的工程中用到了,那么你需要单独下载这些依赖包。
IBM提供了一个检测工具,叫 Migration Toolkit for Application Binaries
,可以扫描应用程序二进制文件(.ear 或 .war 文件)并生成一份报告,突出显示在应用程序中发现的潜在的 Java 11 问题。地址: https://developer.ibm.com/wasdev/downloads/#asset/tools-Migration_Toolkit_for_Application_Binaries
。有兴趣的同学可以自行下载学习。
下面是具体的一些事项:
无效的目标发行版 11
由于在Java 9 之后,每六个月版本会升级一次,如果你依赖的库有处理Java字节码相关的库,应该注意下对应版本的升级。例如:
你可以根据项目工程实际情况,选择添加独立依赖,或者在依赖没有冲突的情况下,把下面所有依赖都添加上。
<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>
Java9引入了模块化,Java Platform Module System,java平台模块系统,简称JPMS。JPMS提供了一个模块化平台,使用来自Java语言规范的访问控制概念来强制实施类型可访问性封装。每个模块都定义了哪些包将被导出,从而可供其他模块访问。一个包可以包含一个 API。如果模块中的包未导出,则表示该模块的开发人员不希望模块外部使用这些包的API。如果外部仍然使用这些包,则会抛出错误!
对于这种错误,最好当然是更换其他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
,对于执行阶段(java),最好把 --add-exports
和 --add-opens
都加上。同时,为了明确所有需要添加的模块和包,可以通过添加 --illegal-access=${value}
来检查。这个value可以填写:
可以通过设置 --illegal-access=deny
来明确需要添加的所有 --add-export
和 --add-open
包。
通过JDK内置的jdeps工具查找过期以及废弃API以及对应的替换:
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)
对于动态加载的类,我们在OpenJDK11中只能自定义类加载器去加载,而不是通过获取APPClassLoader去加载。同时,这么做也有助于你随时能将动态加载的类卸载,因为并没有加载到APPClassLoader。
建议使用自定义的类加载器继承 java.security.SecureClassLoader
去加载类。
如果你想访问classpath下的内容,你可以读取环境变量:
String pathSeparator = System.getProperty("path.separator"); String[] classPathEntries = System.getProperty("java.class.path").split(pathSeparator);
Java8到Java11有很多JVM参数变化。总的来说可以总结为两类参数的变化:一是GC相关的,让GC配置调优更加简单;二是日志相关的,日志统一到了一起,不像之前那么混乱。
https://docs.oracle.com/javase/9/tools/java.htm
,搜索 The following sections describe the options that are obsolete, deprecated, and removed
; https://docs.oracle.com/javase/10/tools/java.htm
,搜索 The following sections describe the options that are obsolete, deprecated, and removed
; https://docs.oracle.com/en/java/javase/11/tools/java.html
,搜索 The following sections describe the options that are obsolete, deprecated, and removed
; 每次的版本变化都需要关注以下三个部分:
这些不推荐的,过时的或移除的参数,如果有替代参数,请使用替代参数。
Java8升级到Java11后,lombok的版本需要升级到 1.18
以上。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
在Java8升级到Java11的过程中,我们的本地编译环境往往同时存在Java8与Java11两套环境。
这里说明一下在本地同时安装有不同版本JDK的时候,如何让Maven同时支持两种Java版本的编译。
假定你已经在本地安装了maven 3.5,在其 conf/settings.xml
中,添加新的profile配置:
<profiles> <!-- 原来的profile --> <profile> <id>jdk-1.8</id> <activation> <!-- 默认激活 --> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> ... </profile> <!-- 为JDK11添加的profile --> <profile> <id>openJDK11</id> <activation> <jdk>11</jdk> </activation> <properties> <JAVA_HOME>/usr/java/jdk-11.0.7+10</JAVA_HOME> <JAVA_VERSION>11</JAVA_VERSION> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <maven.compiler.compilerVersion>11</maven.compiler.compilerVersion> </properties> <repositories> <repository> <id>xxx-Repository</id> <name>xxx Maven Repository</name> <url>maven私服地址</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>xxx-Repository</id> <name>xxx Maven Repository</name> <url>maven私服地址</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles>
如果你是在IDEA中直接用maven插件编译,那么:
1.首先确认项目相关JDK版本是否都已经指定为JDK11:
2.确认IDEA使用了本地maven,且对应的java版本是JDK11:
3.确认Maven插件使用的profile是前面新添加的 openJDK11
:
此时,你就可以使用上图中的 Lifecycle
下的各种maven命令了。
同时你可以看到,maven-compiler-plugin的版本是3.1,并不需要升级到3.8:
如果你本地的环境变量 JAVA_HOME
仍然是之前的版本,比如jdk1.8,但是你又想直接在命令行使用mvn命令对工程进行编译,那么你需要在命令行会话窗口先临时将 JAVA_HOME
改为JDK11,然后再执行mvn命令:
# 先cd到目标目录 cd <工程目录> # 检查mvn当前使用的java版本与Java home mvn -version # 改写JAVA_HOME到jdk11安装目录 export JAVA_HOME=/usr/java/jdk-11.0.7+10 # 重新检查mvn当前使用的java版本与Java home mvn -version # 确认java home已经是JDK11以后,执行mvn命令 mvn clean install package
对于springMVC,springboot,springcloud的工程,Java8升级到Java11后,对应的spring版本也需要升级。
根据Spring官方文档的描述,Spring Framework 5.1.x 或 Spring Boot 2.1.x 开始才支持JDK11。
从 https://spring.io/projects/spring-framework#learn
选择某个版本的 Reference Doc.
,然后选择章节 overview
。
或直接访问地址: https://docs.spring.io/spring/docs/<版本号>/spring-framework-reference/overview.html#overview
,注意把 <版本号>
替换为具体的版本号如 5.0.17.RELEASE
或 5.1.15.RELEASE
。
由此可知,jdk11需要Spring Framework 5.1以上。
从 https://spring.io/projects/spring-boot#learn
选择某个版本的 Reference Doc.
,然后选择章节 9. System Requirements
。
对于历史版本,可以直接访问地址 https://docs.spring.io/spring-boot/docs/<版本号>/reference/html/getting-started-system-requirements.html
,注意把 <版本号>
替换为具体的版本号如 2.1.10.RELEASE
或 2.0.8.RELEASE
。
由此可知,jdk11需要Springboot 2.1以上。
打开 https://spring.io/projects/spring-cloud#overview
,向下翻到 Table 1. Release train Spring Boot compatibility
:
由此可知,Springcloud的 Greenwich SR5
与springboot的 2.1.x
是对应的。
从 https://spring.io/projects/spring-cloud#learn
中选择某个版本的 Reference Doc
(这里我选择的是 Greenwich SR5
),点开选择 Single HTML
,然后在文档中,查找jdk相关章节,发现 JDK 11 Support
中有如下说明:
即,jdk11后,springcloud的Eureka注册中心需要单独引入 jaxb-runtime
的依赖。
对于springboot或springcloud项目来说,为了解决依赖包的版本管理和版本冲突问题,一般不会直接在dependency中显式地写version,而是省略掉version,通过下面两种方法,让spring的版本项目帮我们做版本管理。
一种是默认方式,通过parent标签引入 spring-boot-starter-parent
:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.14.RELEASE</version> </parent>
另一种方式是这里推荐的写法,采用dependencyManagement标签,引入spring的版本兼容的依赖管理项目(常用的是 spring-boot-dependencies
与 spring-cloud-dependencies
):
<dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.14.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> <dependencies> <dependency> <!-- Import dependency management from Spring Cloud --> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
对于前面的章节[3.2 字节码处理相关依赖包的版本升级],[3.3 Java9以后移除模块的依赖对应]和[3.8 Lombok编译异常],如果对应的依赖包已经写进了pom的dependencies,那么Java8升级到Java11之后,你可以先确定通过 spring-boot-starter-parent
或 spring-boot-dependencies
与 spring-cloud-dependencies
所自动管理的依赖包版本是否已经包含了上述章节的依赖包,对应的版本是否已经是支持JDK11的版本。
如果版本是 unknown
,说明 spring-boot-starter-parent
或 spring-boot-dependencies
与 spring-cloud-dependencies
中没有对应的依赖包版本管理,需要显式地写出version;如果有具体的版本号,则说明已经该依赖包已经集成到spring的依赖包版本管理中,你只需要确认该版本是否支持JDK11即可。
例如,pom如下:
... <properties> <spring-boot.version>2.1.10.RELEASE</spring-boot.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> ... </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> ... </dependencies> </dependencyManagement> <dependencies> ... <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> ... </dependencies>
对应的lombok版本:
8.0.18
后,对应驱动class变为 com.mysql.cj.jdbc.Driver
。 5.1.47
版本,也是可以的。此时 driver-class-name
仍然是 com.mysql.jdbc.Driver
。 WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by org.apache.ibatis.reflection.Reflector (file:/home/maven_repo/org/mybatis/mybatis/3.4.6/mybatis-3.4.6.jar) to method java.lang.Integer.getChars(int,int,byte[]) WARNING: Please consider reporting this to the maintainers of org.apache.ibatis.reflection.Reflector 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
将 mybatis-spring-boot-starter
升级到新版本即可解决
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency>