跳到主要内容

JEP 220:模块化运行时映像

概括

重构 JDK 和 JRE 运行时映像以容纳模块并提高性能、安全性和可维护性。定义新的 URI 方案,用于命名运行时映像中存储的模块、类和资源,而不会泄露映像的内部结构或格式。根据需要修改现有规范以适应这些变化。

目标

  • 对存储的类和资源文件采用运行时格式:

    • 比传统 JAR 格式更节省时间和空间,而传统 JAR 格式又基于古老的 ZIP 格式;

    • 可以按模块定位并加载类和资源文件;

    • 可以存储来自 JDK 模块以及库和应用程序模块的类和资源文件;和

    • 可以扩展以适应未来的其他类型的数据,例如预计算的 JVM 数据结构和 Java 类的预编译本机代码。

  • 重构 JDK 和 JRE 运行时映像,以明确区分开发人员、部署人员和最终用户可以依赖并在适当时进行修改的文件,这与实现内部且无需更改即可进行更改的文件形成鲜明对比。注意。

  • 提供受支持的方法来执行常见操作,例如_枚举_图像中存在的所有类,这现在需要检查运行时图像的内部结构。

  • 启用对 JDK 类的选择性_取消特权,_这些类现在已被授予所有安全权限,但实际上并不需要这些权限。

  • 保留行为良好的应用程序的现有行为,_即_不依赖于 JRE 和 JDK 运行时映像内部方面的应用程序。

成功指标

相当于之前 JDK 9 构建的JRE、JDK 和Compact Profile映像的模块化运行时映像不得在启动、静态足迹和动态足迹基准测试的代表性集合上出现退化。

非目标

  • 保留当前运行时映像结构的所有方面并不是目标。

  • 保留所有现有 API 的确切当前行为并不是我们的目标。

动机

Jigsaw 项目旨在为 Java SE 平台设计和实现一个标准模块系统,并将该系统应用于平台本身和 JDK。其主要目标是使平台的实现更容易扩展到小型设备,提高安全性和可维护性,提高应用程序性能,并为开发人员提供更好的大型编程工具。

此 JEP 是 Project Jigsaw 的四个 JEP 中的第三个。早期的JEP 200定义了模块化 JDK 的结构,而JEP 201将 JDK 源代码重新组织为模块。后来的 JEP 261介绍了实际的模块系统。

描述

当前运行时图像结构

JDK 构建系统目前生成两种类型的运行时映像:Java 运行时环境 (JRE),它是 Java SE 平台的完整实现;以及 Java 开发工具包 (JDK),它嵌入 JRE 并包括开发工具和图书馆。 (三个Compact Profile版本是 JRE 的子集。)

JRE镜像的根目录包含bin和两个目录,lib内容如下:

  • bin目录包含基本的可执行二进制文件,特别是java用于启动运行时系统的命令。 (在 Windows 操作系统上,它还包含运行时系统的动态链接本机库。)

  • lib目录包含各种文件和子目录:

    • 各种.properties文件.policy,其中大部分可能(尽管很少)由开发人员、部署人员和最终用户编辑;

    • endorsed默认情况下不存在的目录,可以将包含认可标准和独立技术的实现的 JAR 文件放置在该目录中

    • 可以放置ext包含扩展或可选包的JAR 文件的目录;

    • 各种二进制格式的实现内部数据文件,_例如_字体、颜色配置文件和时区数据;

    • 各种 JAR 文件,包括rt.jar,其中包含运行时系统的 Java 类和资源文件。

    • Linux、macOS 和 Solaris 操作系统上运行时系统的动态链接本机库。

JDK 映像在其子目录中包含 JRE 的副本jre,并包含其他子目录:

  • bin目录包含命令行开发和调试工具,例如javacjavadocjconsole,以及jre/bin目录中的二进制文件的副本,以方便使用;

  • demo目录sample分别包含演示程序和示例代码;

  • man目录包含 UNIX 风格的手册页;

  • include目录包含 C/C++ 头文件,供编译直接与运行时系统交互的本机代码时使用;和

  • lib目录包含各种 JAR 文件和其他类型的文件,其中包含 JDK 工具的实现,其中tools.jar包含编译器的类javac

JDK 映像或未嵌入 JDK 映像中的 JRE 映像的根目录还包含各种COPYRIGHTLICENSEREADME文件,以及一个release以简单键/值属性对来描述映像的文件,例如

JAVA_VERSION="1.9.0"
OS_NAME="Linux"
OS_VERSION="2.6"
OS_ARCH="amd64"

新的运行时映像结构

目前 JRE 和 JDK 映像之间的区别纯粹是历史性的,是 JDK 1.2 版本开发后期做出的实现决策的结果,并且从未重新考虑过。新的镜像结构消除了这种区别:JDK 镜像只是一个运行时镜像,它恰好包含 JDK 中历史上发现的全套开发工具和其他项目。

模块化运行时映像包含以下目录:

  • bin目录包含由链接到映像的模块定义的任何命令行启动器。 (在 Windows 上,它继续包含运行时系统的动态链接本机库。)

  • conf目录包含.properties.policy和其他类型的文件,供开发人员、部署人员和最终用户编辑,这些文件以前可以在该lib目录或其子目录中找到。

  • Linux、macOS 和 Solaris 上的目录lib包含运行时系统的动态链接本机库,就像现在一样。这些名为libjvm.so或 的文件libjvm.dylib可以通过嵌入运行时系统的程序进行链接。该目录中的其他一些文件也供外部使用,包括src.zipjexec.

  • 该目录中的所有其他文件和目录lib必须被视为运行时系统的私有实现细节。它们不适合外部使用,其名称、格式和内容如有更改,恕不另行通知。

  • legal目录包含链接到图像的模块的法律声明,每个模块分为一个子目录。

  • 完整的 JDK 映像还包含 、和demo目录,就像现在一样。 (该目录已被JEP 298删除。)man``include``samples

模块化运行时映像的根目录还包含release由构建系统生成的文件。为了方便判断运行时映像中存在哪些模块,该release文件包含一个新属性 ,MODULES它是这些模块名称的空格分隔列表。该列表根据模块的依赖关系进行拓扑排序,因此该java.base模块始终位于第一个。

删除:认可标准覆盖机制

认可标准覆盖机制允许实现在 Java 社区进程之外维护的较新版本的标准,或者作为 Java SE 平台一部分但继续独立发展的独立 API,以安装到运行时映像中。

认可标准机制是根据类似路径的系统属性java.endorsed.dirs和该属性的默认值来定义的$JAVA_HOME/lib/endorsed。包含认可标准或独立 API 的较新实现的 JAR 文件可以安装到运行时映像中,方法是将其放置在系统属性指定的目录之一中,或者lib/endorsed如果系统属性是,则将其放置在默认目录中没有定义的。此类 JAR 文件在运行时被添加到 JVM 的引导类路径之前,从而覆盖运行时系统本身中存储的任何定义。

模块化映像由模块而不是 JAR 文件组成。展望未来,通过可升级模块的概念,仅以模块化形式支持认可的标准和独立 API 。因此,我们删除了认可标准覆盖机制,包括java.endorsed.dirs系统属性和lib/endorsed目录。为了帮助识别此机制的任何现有用途,如果设置了此系统属性或目录存在,编译器和启动器现在会失败lib/endorsed

删除:扩展机制

扩展机制允许包含扩展 Java SE 平台的 API 的 JAR 文件安装到运行时映像中,以便使用该映像编译或在该映像上运行的每个应用程序都可以看到它们的内容。

机制是根据类似路径的系统属性以及由特定于平台的系统范围目录(例如,在 Linux 上)java.ext.dirs组成的该属性的默认值来定义的。它的工作方式与认可标准机制大致相同,只是放置在扩展目录中的 JAR 文件是由运行时环境的_扩展类加载器_加载的,扩展类加载器是引导类加载器的子加载器和系统类的父加载器loader,它实际上加载要从类路径运行的应用程序。因此,扩展类无法覆盖引导加载程序加载的 JDK 类,但它们会优先于系统加载程序及其后代定义的类进行加载。$JAVA_HOME/lib/ext``/usr/java/packages/lib/ext

扩展机制是在 1998 年发布的 JDK 1.2 中引入的,但在现代我们几乎没有看到它使用的证据。这并不奇怪,因为当今大多数 Java 应用程序将它们所需的库直接放置在类路径上,而不要求将这些库作为运行时系统的扩展安装。

继续支持模块化 JDK 中的扩展机制在技术上是可行的,尽管有些尴尬。为了简化 Java SE 平台和 JDK,我们删除了扩展机制,包括java.ext.dirs系统属性和lib/ext目录。为了帮助识别此机制的任何现有用途,如果设置了此系统属性或目录存在,编译器和启动器现在会失败lib/ext。默认情况下,编译器和启动器会忽略特定于平台的系统范围扩展目录,但如果-XX:+CheckEndorsedAndExtDirs指定了命令行选项,那么如果该目录存在且不为空,它们将失败。

保留了与扩展机制相关的几个功能,因为它们本身很有用:

  • Manifest属性Class-Path,指定另一个 JAR 文件所需的 JAR 文件;

  • 清单{Specification,Implementation}-{Title,Version,Vendor}属性,指定包和 JAR 文件版本信息;

  • ManifestSealed属性,密封包或 JAR 文件;和

  • 扩展类加载器本身,尽管它现在被称为_平台_类加载器

删除:rt.jartools.jar

lib/rt.jar以前存储在、lib/tools.jar、和各种其他内部 JAR 文件中的类和资源文件lib/dt.jar现在以更有效的格式存储在目录中特定于实现的文件中lib。这些文件的格式未指定,如有更改,恕不另行通知。

删除rt.jar类似文件会导致三个不同的问题:

  1. 现有的标准 API(例如ClassLoader::getSystemResource方法返回URL对象以命名运行时映像内的类和资源文件)。例如,当在 JDK 8 上运行时,代码

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

    返回jar表单的 URL

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

    可以看出,它嵌入了一个fileURL 来命名运行时映像中的实际 JAR 文件。getContent该对象的方法可URL用于通过 URL 方案的内置协议处理程序检索类文件的内容jar

    模块化映像不包含任何 JAR 文件,因此上述形式的 URL 没有任何意义。幸运的是,相关方法的规范getSystemResource并不要求URL这些方法返回的对象实际上使用 JAR 方案。然而,它们确实要求可以通过这些URL对象加载存储的类或资源文件的内容。

  2. APIjava.security.CodeSource安全策略文件使用 URL 来命名要授予指定权限的代码库的位置。当前,需要特定权限的运行时系统组件lib/security/java.policy通过fileURL 在文件中标识。椭圆曲线加密提供程序,例如,被标识为

    file:${java.home}/lib/ext/sunec.jar

    显然,这在模块化图像中没有任何意义。

  3. IDE 和其他类型的开发工具需要能够枚举存储在运行时映像中的类和资源文件,并读取其内容。如今,他们经常通过打开和阅读rt.jar类似文件来直接执行此操作。当然,这对于模块化映像来说是不可能的。

用于命名存储模块、类和资源的新 URI 方案

为了解决上述三个问题,jrt可以使用新的 URL 方案来命名运行时映像中存储的模块、类和资源,而无需透露映像的内部结构或格式。

根据RFC 3986 , URLjrt是一个分层 URI,语法如下

jrt:/[$MODULE[/$PATH]]

其中$MODULE是可选模块名称$PATH,如果存在,则是该模块内特定类或资源文件的路径。 URL的含义jrt取决于其结构:

  • jrt:/$MODULE/$PATH$PATH给定的$MODULE.

  • jrt:/$MODULE指模块中的所有类和资源文件$MODULE

  • jrt:/指当前运行时映像中存储的类和资源文件的整个集合。

这三种形式的jrtURL 解决了上述问题,如下所示:

  1. 目前返回 URL 的 APIjar现在返回jrtURL。上面的调用ClassLoader::getSystemResource例如,现在返回 URL

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

    该方案的内置协议处理程序jrt确保getContent此类URL对象的方法检索指定类或资源文件的内容。

  2. 安全策略文件和CodeSourceAPI 的其他用途可以使用jrtURL 来命名特定模块以授予权限。椭圆曲线加密提供程序,例如jrt,现在可以通过URL来识别

    jrt:/jdk.crypto.ec

    当前被授予所有权限但实际上并不需要这些权限的其他模块可以轻松地被取消特权,_即_精确地给出它们所需的权限。

  3. URL 方案的内置NIO FileSystem提供程序确保开发工具可以通过加载由 URL 命名的FileSystemjrt来枚举和读取运行时映像中的类和资源文件,如下所示:jrt:/

    FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
    byte[] jlo = Files.readAllBytes(fs.getPath("modules", "java.base",
    "java/lang/Object.class"));

    该文件系统中的顶级modules目录包含映像中每个模块的一个子目录。顶级packages目录包含映像中每个包的一个子目录,并且该子目录包含指向定义该包的模块的子目录的符号链接。

    对于支持 JDK 9 代码开发但本身在 JDK 8 上运行的工具,适合在 JDK 8 上使用的此文件系统提供程序的副本放置在libJDK 9 运行时映像的目录中名为jrt-fs.jar.

jrtURL 协议处理程序不会返回第二和第三种形式的 URL 的任何内容。)

构建系统更改

构建系统使用 Java 链接器 ( JEP 282 )生成上述新的运行时映像格式。

最后,我们借此机会将images/j2sdk-imageimages/j2re-image目录分别重命名为images/jdkimages/jre

规格略有变化

在 JDK 8 中实现的JEP 162进行了许多更改,以便为 Java SE 平台和 JDK 做好此处和相关 JEP 中描述的模块化工作的准备。这些更改包括删除了要求在lib运行时映像目录中查找某些配置文件的规范规范语句,因为这些文件现在位于该conf目录中。大多数具有此类语句的仅限 SE 的 API 都已作为 Java SE 8 的一部分进行了修订,但跨 Java SE 和 EE 平台共享的一些 API 仍然包含此类语句:

  • javax.xml.stream.XMLInputFactory指定${java.home}/lib/stax.properties( JSR 173 )。

  • javax.xml.ws.spi.Provider指定${java.home}/lib/jaxws.properties( JSR 224 )。

  • javax.xml.soap.MessageFactory和相关类,请指定${java.home}/lib/jaxm.properties( JSR 67 )。

在 Java SE 9 中,这些语句不再强制指定lib目录。

测试

一些现有的测试直接使用运行时图像内部结构(例如rt.jar)或引用不再存在的系统属性(例如, )。java.ext.dirs这些测试已得到修复。

包含此处描述的更改的早期访问版本在模块系统的整个开发过程中都可用。强烈鼓励 Java 社区成员针对这些构建测试他们的工具、库和应用程序,以帮助识别兼容性问题。

风险和假设

该提案的主要风险是兼容性风险,总结如下:

  • jre如上所述,JDK 映像不再包含子目录。假设该目录存在的现有代码可能无法正常工作。

  • 如上所述,JDK 和 JRE 映像不再包含文件lib/rt.jarlib/tools.jar、和其他内部 JAR 文件。lib/dt.jar假定这些文件存在的现有代码可能无法正常工作。

  • 如上所述,系统属性java.endorsed.dirs和不再定义。java.ext.dirs假设这些属性具有非null值的现有代码可能无法正常工作。

  • 运行时系统的动态链接本机库始终位于该lib目录中,Windows 除外;在 Linux 和 Solaris 版本中,它们之前被放置在lib/$ARCH子目录中。这是可以支持多个 CPU 架构的图像的残余,而这不再是一个要求。

  • src.zip文件现在位于该lib目录中,而不是顶级目录中,并且该文件现在包含映像中每个模块的一个目录。读取此文件的 IDE 和其他工具需要更新。

  • 如上所述,现有标准 API 返回URL运行时映像内的名称类和资源文件的对象,现在返回URL。jrt期望这些 API 返回jarURL 的现有代码可能无法正常工作。

  • 内部系统属性sun.boot.class.path 已被删除。依赖于此属性的现有代码可能无法正常工作。

  • JDK 映像中的类和资源文件以前在 中找到lib/tools.jar,并且仅当该文件添加到类路径时才可见,现在可以通过系统类加载器或在某些情况下通过引导类加载器可见。包含这些文件的模块未在应用程序类路径中(_即_系统属性值中)提及java.class.path

  • 以前仅在将文件添加到类路径中时才可见的类和资源文件lib/dt.jar现在可以通过引导类加载器可见,并且存在于 JRE 和 JDK 中。

  • 以前在该目录中找到的配置文件lib(包括安全策略文件)现在位于该conf目录中。检查或操作这些文件的现有代码可能需要更新。

  • 某些现有包中类型的定义类加载器已更改。对这些类型的类加载器进行假设的现有代码可能无法正常工作。具体的变化在JEP 261中列举。其中一些变化是包含 API 和工具的组件模块化的结果。历史上,此类组件的类在rt.jar和之间划分tools.jar,但现在所有此类类都位于单个模块中。

  • JRE 映像中的目录bin包含一些以前只能在 JDK 映像中找到的命令,即appletvieweridljjrunscriptjstatd。与前一项一样,这些更改是包含 API 和工具的组件模块化方式的结果。

依赖关系

此 JEP 是Project Jigsaw的四个 JEP 中的第三个。它依赖于JEP 201,它将 JDK 源代码重新组织为模块,并升级了构建系统来编译模块。它还取决于JEP 162中所做的早期准备工作,并在 JDK 8 中实现。