日期:2025/04/01 09:54来源:未知 人气:56
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的安全性和可维护性产生了负面影响。为了帮助迁移,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内部。
如果有一个较旧的工具或库需要使用经过强封装的内部API,请使用--add-exports运行时选项。您还可以在编译时使用--add导出来访问内部API。
--add exports选项的语法为:
--add-exports
其中<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:模块系统。
一些工具和库使用反射API(setAccessible(true))来尝试访问java.*API的非公共字段和方法。在JDK17上,默认情况下这是不可能的,但您可以使用命令行上的--add-opens选项为特定的工具和库启用它。
--add opens的语法为:
复制
--add-opens
此选项允许<module>打开<package>到<target module>,而不考虑模块声明。
作为一种特殊情况,如果<目标模块>是ALL-UNNAMED,则源包将导出到所有未命名的模块,无论这些模块最初存在还是以后创建。例如:
复制
--add-opens java.management/sun.management=ALL-UNNAMED
此示例允许类路径上的所有代码访问java.management/sun.management包中公共类型的非公共成员。
注意:在Java Web Start的JNLP文件中,必须在--add open和它的值之间包含一个等号。
复制
--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和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为父级的类加载器的应用程序可能需要如前所述进行更改。
以前存储在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中,此选项已过时,使用时会发出警告。
以下错误表示您的系统已配置为使用扩展机制:
复制