跳转到主要内容

JDK8和以后的JDK版本之间进行了重大更改。

每一个新的JavaSE版本都引入了一些与以前版本的二进制、源代码和行为不兼容。JDK9中发生的Java SE平台的模块化以及后来的模块化带来了许多好处,但也带来了许多变化。只使用官方Java SE平台API和受支持的JDK特定API的代码应该可以继续工作而不会发生更改。使用JDK内部API的代码应该继续运行,但应该迁移以使用支持的API。

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

以下部分描述了JDK包中的更改,当您将JDK 8应用程序迁移到以后的JDK版本时,您应该注意这些更改。

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

  • JDK中的强封装
  • 新版本字符串方案
  • 对已安装JDK/JJRE映像的更改
  • 部署
  • 垃圾收集的更改
  • 运行Java小程序
  • 正则表达式匹配中的行为变化
  • 安全管理器已弃用并删除

当您的应用程序在最新版本的JDK上成功运行时,请查看“下一步”,它将帮助您避免未来版本出现问题。

JDK中的强封装

一些工具和库使用反射来访问JDK中仅供内部使用的部分。这种反射的使用对JDK的安全性和可维护性产生了负面影响。为了帮助迁移,JDK9到JDK16允许这种反射继续进行,但发出了关于非法反射访问的警告。然而,JDK17是强封装的,因此默认情况下不再允许这种反射。访问java.*API的非公共字段和方法的代码将抛出InaccessibleObjectException。

请注意,sun.misc和sun.reflect包可供所有JDK版本(包括JDK17)中的工具和库反射。

java启动器选项——非法访问允许从JDK9到JDK16中的JDK内部反射。您可以指定以下参数:

  • --illegal-access=permit:允许类路径上的代码反映JDK8中存在的java.*包的内部。对任何这样的元件的第一次反射访问操作导致发出警告,但在该点之后不发出警告。
  • --illegal-access=warn:导致为每个非法反射访问操作发出警告消息。
  • --illegal-access=debug:导致为每个非法反射访问操作显示警告消息和堆栈跟踪。
  • --illegal-access=deny: 禁用所有非法的反射访问操作,但由其他命令行选项(如--add-opens)启用的操作除外。

许多工具和库已经更新,以避免依赖JDK内部,而是使用JDK8和17之间引入的标准Java API。这意味着--非法访问启动器选项在JDK17中已经过时。JDK17中使用此启动器选项,无论是许可、警告、调试还是拒绝,都不会产生任何效果,只会发出警告消息。

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

  • --add-exports: 如果您有一个较旧的工具或库需要使用经过强封装的内部API,请使用--add-expports运行时选项。您还可以在编译时使用--add导出来访问内部API。
  • --add-opens: 如果您有一个旧的工具或库需要通过反射访问java.*API的非公共字段和方法,那么请使用--add-open选项。

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

--add-exports

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

--add exports选项的语法为:

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

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

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

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

 

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

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

注意:如果类路径上的代码使用反射API(setAccessible(true))试图访问java.*API的非公共字段和方法,那么代码将失败。JDK 17默认情况下不允许这样做。但是,您可以使用--add-opens选项来允许这样做。有关详细信息,请参阅“添加打开”一节。

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

复制

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

您还可以使用AddExportsJAR文件清单属性:

复制

Add-Exports:java.management/sun.management

请小心使用--add exports选项。您可以使用它来访问库模块的内部API,甚至是JDK本身,但这样做的风险自负。如果内部API发生更改或被删除,则库或应用程序将失败。

参见JEP 261:模块系统。

--add-opens

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

--add opens的语法为:

复制

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

此选项允许<module>打开<package>到<target module>,而不考虑模块声明。

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

复制

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

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

注意:在Java Web Start的JNLP文件中,必须在--add open和它的值之间包含一个等号。

复制

<j2se version="10" java-vm-args="--add-opens=<module>/<package>=ALL-UNNAMED"  />

--add之间的等号打开,其值在命令行中是可选的。

新版本字符串方案

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

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

新版本字符串的格式为:

$FEATURE.$INTERIM.$UPDATE.$PATCH

添加了一个简单的Java API来解析、验证和比较版本字符串。请参阅java.lang.Runtime.Version。

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

有关JDK9中引入的版本字符串的更改,请参阅JEP223:NewVersionStringScheme。

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

对已安装JDK/JJRE映像的更改

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

更改JDK和JRE布局

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

JDK 11及更高版本

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

JDK 9和JDK 10

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

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

  • bin:包含二进制可执行文件。
  • conf:包含.properties、.policy和其他类型的文件,供开发人员、部署人员和最终用户编辑。这些文件以前是在lib目录或其子目录中找到的。
  • lib:包含动态链接的库和JDK的完整内部实现。

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

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

新的类加载器实现

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

应用程序类加载器不再是URLClassLoader的实例,而是内部类的实例。它是既不是JavaSE也不是JDK模块的模块中的类的默认加载程序。

扩展类加载程序已被重命名;它现在是平台类加载器。Java SE平台中的所有类都保证通过平台类加载器可见。

仅仅因为一个类通过平台类加载器可见,并不意味着该类实际上是由平台类加载器定义的。Java SE平台中的一些类由平台类加载器定义,而其他类则由引导类加载器定义。应用程序不应该依赖于哪个类加载器定义哪个平台类。

JDK9中实现的更改可能会影响创建以null(即引导类加载器)作为父类加载器的类加载器的代码,并假设所有平台类都对父类可见。这样的代码可能需要更改以使用平台类加载器作为父类(请参见ClassLoader.getPlatformClassLoader)。

平台类加载器不是URLClassLoader的实例,而是内部类的实例。

引导类加载器仍然内置于Java虚拟机中,并在ClassLoader API中用null表示。它定义了一些关键模块中的类,如java.base。因此,它定义的类比JDK8中定义的要少得多,因此使用-Xbootclasspath/a部署的应用程序或创建以null为父级的类加载器的应用程序可能需要如前所述进行更改。

删除了rt.jar和tools.jar

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

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

从JDK9开始,ClassLoader.getSystemResource不会返回指向JAR文件的URL(因为没有JAR文件)。相反,它返回一个jrt URL,命名存储在运行时映像中的模块、类和资源,而不透露映像的内部结构或格式。

例如:


复制

ClassLoader.getSystemResource(“java/lang/Class.Class”);

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


复制

jar:文件:/usr/local/jdk8/jre/lib/rt.jar/java/lang/Class.Class

它嵌入一个文件URL来命名运行时映像中的实际JAR文件。

模块化映像不包含任何JAR文件,因此这种形式的URL毫无意义。在JDK9及更高版本上,此方法返回:

复制

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

java.security.CodeSource API和安全策略文件使用URL来命名要授予特定权限的代码库的位置。请参阅Java平台中的策略文件语法,标准版安全开发人员指南。运行时系统中需要特定权限的组件当前通过使用文件URL在conf/security/java.policy文件中进行标识。

旧版本的IDE和其他开发工具需要能够枚举存储在运行时映像中的类和资源文件,并通过打开和读取rt.jar和类似文件直接读取它们的内容。这在模块化图像中是不可能的。

删除了扩展机制

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

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

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


复制

<JAVA_HOME>/lib/ext存在,不再支持扩展机制;请改用-classpath。

。错误:无法创建Java虚拟机。

错误:发生致命异常。程序将退出。

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

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

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

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

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

从JDK9开始,您可以使用可升级模块或将JAR文件放在类路径上。

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

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

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

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


复制

不支持<JAVA_HOME>/lib/approved。认可的标准和独立API

模块化形式将通过可升级模块的概念得到支持。

错误:无法创建Java虚拟机。

错误:发生致命异常。程序将退出。

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

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

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

删除了macOS特定功能

本节包括从JDK9开始删除的macOS特定功能。

特定于平台的桌面功能

java.awt.Desktop类包含对Apple特定的com.Apple.eawt和com.Apple.eio包中API的替换。新的API取代了macOS API,并且是独立于平台的。

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

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

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

已删除AppleScript引擎

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

AppleScript引擎在最近的版本中基本上不可用。该功能仅在JDK 7或JDK 8中有效,这些系统上已经有苹果版本的AppleScriptEngine.jar文件。

Windows注册表项更改

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

“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK”

“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\16”

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

“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK”

“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\16”

“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK\15.0.1”

部署

Java部署技术在JDK9中被弃用,在JDK11中被删除。

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

删除了启动时JRE版本选择

从JDK9开始,请求不是在启动时启动的JRE版本的JRE的功能被删除。

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

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

从JDK 9开始,java启动器被修改如下:

如果在命令行中给定-version:选项,则发出错误消息并退出。

如果在JAR文件中找到JRE版本清单条目,则发出警告消息并继续。

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

删除了对序列化小程序的支持

从JDK9开始,不支持将小程序部署为序列化对象。使用现代压缩和JVM性能,以这种方式部署小程序没有任何好处。

启动小程序时,会忽略小程序标记的对象属性以及对象和java对象小程序参数标记。

使用标准部署策略,而不是序列化小程序。

JNLP规范更新

JNLP(Java网络启动协议)已经更新,以消除不一致,使代码维护更容易,并增强安全性。

在JDK9中,JNLP的更新如下:

  • &amp;而不是JNLP文件中的&。

JNLP文件语法符合XML规范,所有JNLP文件都应该能够由标准XML解析器解析。

JNLP文件允许您指定复杂的比较。以前,这是通过使用“与”符号(&)来完成的,但标准XML不支持这样做。如果您使用&来创建复杂的比较,请将其替换为&amp;在您的JNLP文件中&amp;与所有版本的JNLP兼容。

  • 将数字版本的元素类型与非数字版本元素类型进行比较。

以前,当一个int版本元素与另一个无法解析为int的版本元素进行比较时,版本元素会按照ASCII值进行字典比较。

如果可以解析为int的元素是比另一个元素更短的字符串,则在按ASCII值进行字典比较之前,它将填充前导零。这样可以确保不存在圆形。

在同时使用版本比较和JNLPservlet的情况下,应该只使用数值来表示版本。

  • 在java(或j2se)元素中具有嵌套资源的组件扩展。

这在规范中是允许的。它以前是受支持的,但这种支持没有反映在规范中。

  • FX XML扩展。

对JNLP规范进行了增强,为application-desc元素添加了一个type属性,并在application-desc中添加了子元素param(就像在applet-desc中一样)。

这不会对现有应用程序造成问题,因为仍然支持以前指定JavaFX应用程序的方式。

请参阅JSR-056中的JNLP规范更新。

垃圾收集的更改

本节介绍从JDK9开始对垃圾收集的更改。

将G1设为默认垃圾回收器

Garbage-FirstGarbageCollector(G1GC)是JDK9及更高版本中的默认垃圾收集器。

对于大多数用户来说,像G1GC这样的低暂停收集器应该比像ParallelGC这样的面向吞吐量的收集器(JDK8默认值)提供更好的整体体验。

有关调整G1 GC的更多信息,请参阅Java平台中G1 GC的人机工程学默认值和可调整默认值,标准版热点虚拟机垃圾收集调整指南。

删除了GC选项

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

  • DefNew+CMS
  • ParNew+SerialOld
  • 增量CMS

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

命令行标志-XX:+UseParNewGC不再有效。ParNew标志只能与CMS一起使用,CMS需要ParNew。因此,-XX:+UseParNewGC标志已被弃用,可以在将来的版本中删除。

请参阅JEP 214:删除JDK 8中不推荐的GC组合。

注意:CMS垃圾收集器已被删除。请参阅JEP 363:删除并发标记扫描(CMS)垃圾收集器。

删除永久代(Permanent Generation)

JDK8中删除了永久代,相关的VM选项导致打印警告。您应该从脚本中删除以下选项:

-XX: MaxPermSize=大小

-XX: PermSize=大小

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

复制

Java HotSpot(TM)64位服务器VM警告:忽略选项MaxPermSize;8.0中删除了支持

了解永久生成的工具可能需要更新。

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

GC日志输出的更改

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

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

请参阅Java开发工具包工具规范中的“使用JVM统一日志记录框架启用日志记录”和JEP 271:统一GC日志记录。

运行Java小程序

如果您仍然依赖小程序,则可以在Internet Explorer模式下使用JRE 8和Microsoft Edge在Windows系统上启动它们。请参阅Microsoft Edge+Internet Explorer模式:入门指南。

截至2021年9月,启动Applets所需的Java插件仍在Java 8的Windows上更新,但可能在未来的更新版本中随时删除。

Oracle客户可以在My.Oracle.Support Note 251148.1-Java SE 8 Java插件结束支持(需要登录)上找到更多信息。

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

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

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

然而,在JDK9及更高版本中,否定运算符被应用于所有嵌套的字符类。例如,[^a-b[c-d]e-f]与c不匹配。

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

复制

[^a-d&&c-f]

在JDK8中,^运算符首先应用,因此此示例被解释为[^a-d]与[c-f]相交。这匹配e和f,但不匹配a、b、c或d。

在JDK9及更高版本中,&&运算符首先应用,因此此示例被解释为[a-d]&&[c-f]的补码。这匹配a、b、e和f,但不匹配c或d。

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

安全管理器已弃用并删除

在JDK17中,安全管理器和与其相关的API已被弃用,并将在未来的版本中删除。安全管理器无法替代。讨论和备选方案见JEP 411。

 
 

标签