没耐心的可以直接跳到后面的实战准备工作下载新版本的JDK

从JAVA SE 下载下载并安装新的 JDK 版本。

在重新编译之前运行你的程序

尝试在最新的 JDK 版本 上运行您的应用程序。大多数代码和库无需任何更改即可在 新版本上运行,但可能有一些库需要升级。

当您运行应用程序时,请查看来自 JVM 的有关过时 VM 选项的警告。如果 VM 无法启动,则查找Removed GC Options。

如果您的应用程序成功启动,请仔细查看您的测试并确保其行为与您一直使用的 JDK 版本相同。例如,一些早期采用者注意到他们的日期和货币格式不同。请参阅默认使用 CLDR 区域设置数据。

要使您的代码在最新的 JDK 版本上运行,请了解每个 JDK 版本中的新功能和更改。

即使您的程序看起来运行成功,您也应该完成本指南中的其余步骤并查看问题列表。

更新第三方库

对于您使用的每个工具和第三方库,您可能需要一个支持最新 JDK 版本的更新版本。

检查第三方库和工具供应商的网站,了解每个库或工具的版本,这些库或工具设计用于最新的 JDK。如果存在,则下载并安装新版本。

如果您使用 Maven 或 Gradle 构建应用程序,请确保升级到支持最新 JDK 版本的最新版本。

如果您使用 IDE 开发应用程序,那么它可能有助于迁移现有代码。NetBeans、Eclipse 和 IntelliJ IDE 都有可用的版本,包括对最新 JDK 的支持。

您可以在 OpenJDK wiki 上的 Quality Outreach上查看使用 OpenJDK 构建的许多免费开源软件 (FOSS) 项目的测试状态。

如果需要,编译您的应用程序

使用新版本的 JDK 编译器编译您的代码将简化向新版本的迁移,因为代码可能依赖于已被确定为有问题的 API 和特性。但是,这不是绝对必要的。

如果您需要使用 JDK 11 和更高版本的编译器编译代码,请注意以下几点:

举个例子:

static Object _ = new Object();

此代码从编译器生成以下错误消息:

MyClass.java:2: error: as of release 9, '_' is a keyword, and may not be used as a legal identifier.

支持的-source/-target 值为 17(默认值)、16、15、14、13、12、11、10、9、8 和 7。

在 JDK 8 中,不推荐设置 -source和-target的值为1.5/5或更早的值,如果设置这样值有警告。在 JDK 9 及更高版本中,这些值会直接抛出Error。

>javac -source 5 -target 5 Sample.java warning: [options] bootstrap class path not set in conjunction with -source 5 error: Source option 5 is no longer supported. Use 6 or later. error: Target option 1.5 is no longer supported. Use 1.6 or later.

使用新的--release标志而不是-source -target选项。请参阅Java Development Kit Tool Specifications中的javac。

—release标志的有效值和-source -target选项相同。

javac命令可以识别和处理大于等于 JDK 1.0.2 版本源文件。

请参阅JEP 182:停用 javac -source 和 -target 选项的策略。

识别依赖关系,请运行 java 依赖关系分析工具。如果可能,请更新您的代码以使用支持的替换 API。

您可以使用--add-exports和--add-opens 选项作为临时解决方法来编译引用 JDK 内部类的源代码。有关这些选项的更多信息, 请参阅JEP 261:JDK 中的模块系统和强封装。

在您的代码上运行 jdeps

在您的应用程序上运行该jdeps工具以查看您的应用程序和库所依赖的包和类。如果您使用内部 API,则jdeps可能会建议替换以帮助您更新代码。

要查找对内部 JDK API 的依赖关系,请jdeps使用该-jdkinternals选项运行。例如,在调用了sun.misc.BASE64Encoder的类上运行jdeps,您将看到:

>jdeps -jdkinternals Sample.class Sample.class -> JDK removed internal API Sample -> sun.misc.BASE64Encoder JDK internal API (JDK removed internal API) Warning: JDK internal APIs are unsupported and private to JDK implementation that are subject to be removed or changed incompatibly and could break your application. Please modify your code to eliminate dependency on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check: https://wiki.openjdk.java.net/display/JDK8/Java Dependency Analysis Tool JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8

如果您使用 Maven,则有一个jdeps 可用的插件。

有关jdeps语法,请参阅jdepsJava开发工具包工具规范

但是注意:jdeps是静态分析工具,代码的静态分析可能无法提供完整的依赖关系列表。比如如果代码使用反射来调用内部 API,则jdeps无法发出警告。

迁移

JDK 8 和更高版本的 JDK 之间发生了重大变化。

每个新的 Java SE 版本都会引入一些与以前版本的二进制、源代码和行为不兼容的问题。JDK 9 及之后的 Java SE 平台的模块化带来了许多好处,但也带来了许多变化。仅使用官方 Java SE 平台 API 和受支持的特定于 JDK 的 API(supported JDK-specific APIs) 的代码应继续工作而无需更改。使用 JDK 内部 API 的代码应继续运行,但应迁移以使用受支持的 API。

某些 API 在其默认行为中已被设置为不可访问、删除或更改。编译或运行应用程序时可能会遇到问题。请参阅 已删除的工具和组件以及安全更新。

以下部分描述了将 JDK 8 应用程序迁移到更高版本的 JDK 时应注意的 JDK 包中的更改。

查看运行应用程序时可能遇到的更改列表。

JDK中的强封装

一些工具和库使用反射来访问仅供内部使用的 JDK 部分。这种反射的使用会对 JDK 的安全性和可维护性产生负面影响。为了帮助迁移,JDK 9 到 JDK 16 允许这种反射继续进行,但发出有关非法反射访问的警告。但是,JDK 17 是 强封装(strongly encapsulated)的,所以默认情况下不再允许这种反射。访问 API 的非public字段和非public方法的代码java.*将抛出 InaccessibleObjectException.

请注意,所有 JDK 版本(包括 JDK 17)中的工具和库都可以使用sun.misc和sun.reflect 包进行反射。

java启动选项--illegal-access 允许在 JDK 9 到 JDK 16 中使用反射调用JDK 内部Api。您可以指定以下参数:

许多工具和库已更新以避免依赖 JDK 内部Api,而是使用在 JDK 8 和 17 之间引入的标准 Java API。同时--illegal-access启动选项在 JDK 17 中已过时。在 JDK 17 中使用此启动器选项,无论是使用permit, warn,debug或deny, 除了发出警告消息外没有任何作用。

如果您无法获取或部署较新版本的工具和库,则有两个命令行选项可让您授予对旧版本工具和库的特定内部 API 的访问权限:

请参阅JEP 403:默认情况下强封装 JDK 内部。

--add-exports

如果您有一个较旧的工具或库需要使用已被强封装的内部 API,请使用 --add-exports运行时选项。您还可以--add-exports在编译时使用来访问内部 API。

该--add-exports选项的语法是:

--add-exports <source-module>/<package>=<target-module>(,<target-module>)*

其中 <source-module><target-module>是模块名称,<package>是包的名称。

--add-exports如果目标模块读取源模块, 该选项允许目标模块中的代码访问源模块的命名包中的类型。

作为一种特殊情况,如果<target-module>是 ALL-UNNAMED,则源包将导出到所有未命名的模块,无论它们最初存在还是稍后创建。例如:

--add-exports java.management/sun.management=ALL-UNNAMED

此示例允许所有未命名模块中的代码(类路径上的代码)访问 java.management/sun.management。

如果类路径上的代码使用反射 API (setAccessible(true)) 尝试访问java.*API 的非公共字段和方法,则代码将失败。默认情况下,JDK 17 不允许这样做。但是,您可以使用该 --add-opens选项来允许这样做。有关更多信息,请参见--add-opens部分。

如果在类路径上运行的应用程序oldApp必须使用模块的未导出com.sun.jmx.remote.internal包java.management,则可以通过以下方式授予它所需的访问权限:

--add-exports java.management/com.sun.jmx.remote.internal=ALL-UNNAMED

您还可以使用Add-ExportsJAR 文件清单属性:

Add-Exports:java.management/sun.management

谨慎使用该--add-exports选项。您可以使用它来访问库模块甚至 JDK 本身的内部 API,但这样做的风险由您自己承担。如果该内部 API 更改或被删除,那么您的库或应用程序将失败。

参见JEP 261:模块系统。

--add-opens

一些工具和库使用反射 API (setAccessible(true)) 尝试访问java.*API 的非公共字段和方法。默认情况下,这在 JDK 17 上不再可能,但您可以使用--add-opens命令行上的选项为特定工具和库启用它。

--add-opens语法如下:

--add-opens <module>/<package>=<target-module>(,<target-module>)*

无论模块声明如何, 此选项都允许<module>打开<package><target-module>。

作为一种特殊情况,如果<target-module> 是ALL-UNNAMED,则源包将导出到所有未命名的模块,无论它们最初存在还是稍后创建。例如:

--add-opens java.management/sun.management=ALL-UNNAMED

此示例允许类路径上的所有代码访问java.management/sun.management包中公共类型的非公共成员。

新版本字符串方案

JDK 10 对 JDK 9 中引入的版本字符串方案引入了一些小的更改,以更好地适应基于时间的发布模型。JDK 11 及更高版本保留了 JDK 10 中引入的版本字符串格式。

如果您的代码依赖于版本字符串格式来区分主要、次要、安全和补丁更新版本,那么您可能需要更新它。

新版本字符串的格式为:

$FEATURE.$INTERIM.$UPDATE.$PATCH

添加了用于解析、验证和比较版本字符串的简单 Java API。看java.lang.Runtime.Version.

请参阅Java 平台中的版本字符串格式,标准版安装指南

有关 JDK 9 中引入的版本字符串的更改,请参阅 JEP 223:新版本字符串方案。

有关 JDK 10 中引入的版本字符串更改,请参阅JEP 322: Time-Based Release Versioning。

已安装的 JDK/jre 映像的更改

对 JDK 和 JRE 进行了重大更改。

更改了 JDK 和 JRE 布局

安装 JDK 后,如果查看文件系统,您会注意到目录布局与 JDK 9 之前的版本不同。

JDK 11 及更高版本

JDK 11 及更高版本没有 JRE 映像。请参阅Java 平台中 JDK的已安装目录结构,标准版安装指南

DK 9 和 JDK 10

以前的版本有两种类型的运行时映像:JRE,它是 Java SE 平台的完整实现,以及 JDK,它将整个 JRE 包含在一个jre/目录中,以及开发工具和库。

在 JDK 9 和 JDK 10 中,JDK 和 JRE 是两种类型的模块化运行时映像,包含以下目录:

在 JDK 9 和 JDK 10 中,仍然有单独的 JDK 和 JRE 下载,但每个都有相同的目录结构。JDK 映像包含历史上在 JDK 中发现的额外工具和库。没有jdk/与jre/包装器目录,并且二进制文件(例如java命令)不重复。

请参阅JEP 220:模块化运行时映像。

新的类加载器实现

JDK 9 和更高版本维护了自 1.2 版本以来存在的类加载器的层次结构。但是,为了实现模块系统,进行了以下更改:

删除了 rt.jar 和 tools.jar

以前存储在lib/rt.jar、lib/tools.jar中的类和资源文件lib/dt.jar以及各种其他内部 JAR 文件以更有效的格式存储在lib目录中特定于实现的文件中。

删除rt.jar和类似文件会导致以下方面的问题:

例如:

ClassLoader.getSystemResource("java/lang/Class.class");

在 JDK 8 上运行时,此方法返回以下形式的 JAR URL:

jar:file:/usr/local/jdk8/jre/lib/rt.jar!/java/lang/Class.class

模块化的镜像(modular image)不包含任何 JAR 文件,因此这种形式的 URL 没有意义。在 JDK 9 及更高版本上,此方法返回:

jrt:/java.base/java/lang/Class.class

移除了扩展机制

在 JDK 8 及更早版本中,扩展机制使运行时环境可以查找和加载扩展类,而无需在类路径上专门命名它们。从 JDK 9 开始,如果您需要使用扩展类,请确保 JAR 文件位于类路径上。

在 JDK 9 和 JDK 10 中,如果设置了系统属性或目录存在,javac编译器和java 启动器将退出。要另外检查特定于平台的系统范围目录,请指定 命令行选项。如果目录存在且不为空,这将导致发生相同的退出行为。扩展类加载器保留在 JDK 9(及更高版本)中,并被指定为平台类加载器(参见java.ext.dirslib/ext-XX: CheckEndorsedAndExtDirsgetPlatformClassLoader.) 但是,在 JDK 11 中,此选项已过时,使用时会发出警告。

以下错误表示您的系统配置为使用扩展机制:

<JAVA_HOME>/lib/ext exists, extensions mechanism no longer supported; Use -classpath instead. .Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.

如果设置了java.ext.dirs系统属性, 您将看到类似的错误。

要修复此错误,请删除ext/目录或java.ext.dirs系统属性。

请参阅JEP 220:模块化运行时映像。

删除了经认可的标准覆盖机制

java.endorsed.dirs系统属性和lib/endorsed目录将不再存在。如果检测到任何一个,编译器和启动器将退出javac。java

从 JDK 9 开始,您可以使用可升级模块或将 JAR 文件放在类路径中实现类似功能。

此机制旨在让应用程序服务器覆盖 JDK 中使用的组件。要更新的包将被放入 JAR 文件中,系统属性java.endorsed.dirs会告诉 Java 运行时环境在哪里可以找到它们。如果未指定此属性的值,则使用默认值$JAVA_HOME/lib/endorsed。

在 JDK 8 中,您可以使用-XX: CheckEndorsedAndExtDirs命令行参数来检查系统上任何位置的此类目录。

在 JDK 9 及更高版本中,如果设置了java.endorsed.dirs系统属性或lib/endorsed目录存在 ,javac编译器和java启动器将退出。

以下错误意味着您的系统配置为使用认可的标准覆盖机制:

<JAVA_HOME>/lib/endorsed is not supported. Endorsed standards and standalone APIs in modular form will be supported via the concept of upgradeable modules. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.

如果设置了java.endorsed.dirs系统属性, 您将看到类似的错误。

要修复此错误,请删除lib/endorsed目录或取消设置java.endorsed.dirs系统属性。

请参阅JEP 220:模块化运行时映像。

删除了 macOS 特定的功能

从 JDK 9 开始删除了macOS 特定功能。

特定于平台的桌面功能

该类包含对 Apple 特定的和包java.awt.Desktop中 API 的替换。新的 API 取代了 macOS API,并且独立于具体操作系统。 com.apple.eawtcom.apple.eio

com.apple.eawt和包中的 APIcom.apple.eio是封装的,因此您将无法在 JDK 9 或更高版本中针对它们进行编译。但是,它们在运行时仍可访问,因此编译为旧版本的现有代码继续运行。apple最终,使用andcom.apple 包及其子包 中的内部类的库或应用程序 将需要迁移到新的 API。

com.apple.concurrent 和包被删除,apple.applescript没有任何替换。

请参阅JEP 272:特定于平台的桌面功能。

移除 AppleScript 引擎

AppleScript 引擎,特定于平台的javax.script实现,已在 JDK 中删除,没有任何替换。

AppleScript 引擎在最近的版本中几乎无法使用。该功能仅在 JDK 7 或 JDK 8 中有效,系统上已经有 Apple 版本的AppleScriptEngine.jar文件。

Windows 注册表项更改

Java 11 及更高版本的安装程序会在安装 JDK 时创建 Windows 注册表项。对于 JDK 16,安装程序会创建以下 Windows 注册表项:

如果安装了两个版本的 JDK,则会创建两个不同的 Windows 注册表项。例如,如果 JDK 15.0.1 与 JDK 16 一起安装,则安装程序会创建另一个 Windows 注册表项,如下所示:

部署

Java 部署技术在 JDK 9 中被弃用,并在 JDK 11 中被删除。

Java Applet 和 WebStart 功能,包括 Applet API、Java 插件、Java Applet 查看器、JNLP 和 Java Web Start(包括 javaws 工具)在 JDK 9 中均已弃用

使用jlinkJDK 9 引入的工具来打包和部署专用运行时,而不是依赖于预安装的系统 JRE。

删除了启动时 JRE 版本选择功能

从 JDK 9 开始,删除了请求不是在启动时启动的 JRE 的 JRE 版本的能力。

现代应用程序通常使用 Java Web Start (JNLP)、本机 OS 打包系统或活动安装程序进行部署。这些技术有自己的方法来管理所需的 JRE,方法是根据需要查找或下载和更新所需的 JRE。这使得启动器的启动时可以选择已过时JRE 版本。

在以前的版本中,您可以指定启动应用程序时要使用的 JRE 版本(或版本范围)。可以通过命令行选项和应用程序 JAR 文件中的清单条目来选择版本。

从 JDK 9 开始,java launcher 修改如下:

请参阅JEP 231:删除启动时 JRE 版本选择。

垃圾收集的变化将 G1 设为默认垃圾收集器删除了部分GC 选项

以下 GC 组合将导致您的应用程序在 JDK 9 及更高版本中无法启动:

CMS 的前台模式也已被删除。删除的命令行标志是-Xincgc、-XX: CMSIncrementalMode、 -XX: UseCMSCompactAtFullCollection、-XX: CMSFullGCsBeforeCompaction和 -XX: UseCMSCollectionPassing。

命令行标志-XX: UseParNewGC不再有效。该ParNew标志只能用于 CMS 和 CMS 需要ParNew。因此,该-XX: UseParNewGC标志已被弃用,并且在未来的版本中删除。

请参阅JEP 214:删除 JDK 8 中已弃用的 GC 组合。

删除了永久代

JDK 8 中删除了永久代,相关的 VM 选项会导致打印警告。您应该从脚本中删除这些选项:

在 JDK 9 及更高版本中,JVM 会显示如下警告:

Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option MaxPermSize; support was removed in 8.0

使用了永久代参数的工具可能必须更新。

请参阅JEP 122:删除永久代和JDK 9 发行说明 - 删除的 API、功能和选项。

GC 日志输出的更改

垃圾收集(GC)日志使用JVM统一的日志框架,新旧日志存在一些差异。您正在使用的任何 GC 日志解析器都可能需要更改。

您可能还需要更新 JVM 日志记录选项。所有与 GC 相关的日志记录都应使用 gc标签(例如—Xlog:gc),通常与其他标签结合使用。和选项已被弃用 —XX: PrintGCDetails。-XX: PrintGC

请参阅Java Development Kit Tool Specifications和JEP 271: Unified GC Logging中的使用 JVM Unified Logging Framework 启用日志记录。

正则表达式匹配中的行为变化

java.util.regex.Pattern用方括号定义正则表达式中的字符类。例如,[abc] 匹配a,b,或c。否定字符类是用紧跟在左大括号后面的插入符号定义的。例如, [^abc]匹配除a,b, 或之外的任何字符c。

在 JDK 8 及更早版本中,否定字符类不会否定嵌套字符类。例如,[^a-b[c-d]e-f]匹配 c但不匹配a,或者 e因为它们不在嵌套类中。运算符一个接一个地应用。^在此示例中,在嵌套之前应用了否定运算符。在 JDK 8 及更早的版本中,运算符^仅应用于字符类中的最外层字符,而不应用于到嵌套的字符类。这种行为令人困惑且难以理解。

但是,在 JDK 9 及更高版本中,否定运算符应用于所有嵌套字符类。例如, [^a-b[c-d]e-f]不匹配 c。

为了进一步解释,请考虑以下正则表达式:

[^a-d&&c-f]

在 JDK 8 中,^首先应用运算符,因此该示例被解释为 [^a-d] 与[c-f]. 这匹配 e and f但不 匹配a, b, c, or d。

在 JDK 9 及更高版本中,&&首先应用运算符,因此该示例被解释为[a-d]&&[c-f]. 这匹配 a, b,e和f但不匹配c or d。

作为最佳实践,寻找使用字符类的正则表达式以及否定、交集和嵌套类的某种组合。可能需要调整这些正则表达式以考虑更改的行为。

后续步骤实战目标

使用JDK 17 构建hutool,新建springboot工程并调用JDK 17调用hutool中的功能。

步骤

先用JDK 17直接构建,修改pom.xml,将JDK版本修改为17

首先确保已经安装配置好JDK 17:这里使用了更高版本的19

jdk版本怎么更新(8升级高版本JDK指南)(1)

jdk版本怎么更新(8升级高版本JDK指南)(2)

运行mvn clean compile进行编译

jdk版本怎么更新(8升级高版本JDK指南)(3)

编译失败,因为javax.xml包已经被移除。

在hutool-core/pom.xml中添加javax.xml的依赖

<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>

重新编译,全部编译成功

jdk版本怎么更新(8升级高版本JDK指南)(4)

执行mvn install -Dmaven.javadoc.skip=true 执行单元测试并本地安装hutool,执行单元测试在这里很重要,因为不能保证升级JDK之后,代码逻辑不受影响。

jdk版本怎么更新(8升级高版本JDK指南)(5)

对于Unable to make protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not "opens java.lang" to unnamed module @5a65309b 错误,明显是因为Java 9开始的模块系统强封装造成的,我们需要添加--add-exports。

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine> </configuration> </plugin>

添加上面插件和配置之后,java.lang.CloneNotSupportedException不再出现,现在来处理java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached。这个错误是因为要创建的线程数超过了系统的限制,调大即可。

解决这个错误之后,出现module java.base does not "opens java.util.regex" to unnamed module @15615099

jdk版本怎么更新(8升级高版本JDK指南)(6)

继续添加opens

jdk版本怎么更新(8升级高版本JDK指南)(7)

出现新的错误,这是因为JDK移除了javax.activation,添加依赖就好

jdk版本怎么更新(8升级高版本JDK指南)(8)

添加如下依赖

jdk版本怎么更新(8升级高版本JDK指南)(9)

出现新的错误java.lang.NoSuchFieldException: modifiers

jdk版本怎么更新(8升级高版本JDK指南)(10)

因为JDK 12开始,field字段不再有modifiers,所以更改代码以满足要求

jdk版本怎么更新(8升级高版本JDK指南)(11)

再次重新构建,出现新的错误

jdk版本怎么更新(8升级高版本JDK指南)(12)

添加opens:

jdk版本怎么更新(8升级高版本JDK指南)(13)

再次构建,出现如下错误

jdk版本怎么更新(8升级高版本JDK指南)(14)

debug之后发现,这个错误是因为高版本jdk使用MemberName来判断field的modifier,让前面代码中修改失败,所以只能先暂时跳过这个测试并不使用这个功能,等待项目组更新版本支持高版本JDK。

jdk版本怎么更新(8升级高版本JDK指南)(15)

至此构建全部成功

jdk版本怎么更新(8升级高版本JDK指南)(16)

新建SpringBoot工程

jdk版本怎么更新(8升级高版本JDK指南)(17)

引入之前构建在本地的hutool

jdk版本怎么更新(8升级高版本JDK指南)(18)

编写一个简单的测试类:

@RestController public class TestController { @GetMapping("/test/{plainText}") public ResponseEntity test(@PathVariable("plainText") String plainText) { int hash = HashUtil.apHash(plainText); return ResponseEntity.ok(hash); } }

启动应用进行测试:

jdk版本怎么更新(8升级高版本JDK指南)(19)

,