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
目录包含各种文件和子目录:
JDK 映像在其子目录中包含 JRE 的副本jre
,并包含其他子目录:
-
该
bin
目录包含命令行开发和调试工具,例如、javac
、javadoc
和jconsole
,以及jre/bin
目录中的二进制文件的副本,以方便使用; -
和
demo
目录sample
分别包含演示程序和示例代码; -
该
man
目录包含 UNIX 风格的手册页; -
该
include
目录包含 C/C++ 头文件,供编译直接与运行时系统交互的本机代码时使用;和 -
该
lib
目录包含各种 JAR 文件和其他类型的文件,其中包含 JDK 工具的实现,其中tools.jar
包含编译器的类javac
。
JDK 映像或未嵌入 JDK 映像中的 JRE 映像的根目录还包含各种COPYRIGHT
、LICENSE
和README
文件,以及一个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.zip
和jexec
. -
该目录中的所有其他文件和目录
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 文件版本信息; -
Manifest
Sealed
属性,密封包或 JAR 文件;和 -
扩展类加载器本身,尽管它现在被称为_平台_类加载器。
删除:rt.jar
和tools.jar
lib/rt.jar
以前存储在、lib/tools.jar
、和各种其他内部 JAR 文件中的类和资源文件lib/dt.jar
现在以更有效的格式存储在目录中特定于实现的文件中lib
。这些文件的格式未指定,如有更改,恕不另行通知。
删除rt.jar
类似文件会导致三个不同的问题:
-
现有的标准 API(例如
ClassLoader::getSystemResource
方法返回URL
对象以命名运行时映像内的类和资源文件)。例如,当在 JDK 8 上运行时,代码ClassLoader.getSystemResource("java/lang/Class.class");
返回
jar
表单的 URLjar:file:/usr/local/jdk8/jre/lib/rt.jar!/java/lang/Class.class
可以看出,它嵌入了一个
file
URL 来命名运行时映像中的实际 JAR 文件。getContent
该对象的方法可URL
用于通过 URL 方案的内置协议处理程序检索类文件的内容jar
。模块化映像不包含任何 JAR 文件,因此上述形式的 URL 没有任何意义。幸运的是,相关方法的规范
getSystemResource
并不要求URL
这些方法返回的对象实际上使用 JAR 方案。然而,它们确实要求可以通过这些URL
对 象加载存储的类或资源文件的内容。 -
API
java.security.CodeSource
和安全策略文件使用 URL 来命名要授予指定权限的代码库的位置。当前,需要特定权限的运行时系统组件lib/security/java.policy
通过file
URL 在文件中标识。椭圆曲线加密提供程序,例如,被标识为file:${java.home}/lib/ext/sunec.jar
显然,这在模块化图像中没有任何意义。
-
IDE 和其他类型的开发工具需要能够枚举存储在运行时映像中的类和资源文件,并读取其内容。如今,他们经常通过打开和阅读
rt.jar
类似文件来直接执行此操作。当然,这对于模块化映像来说是不可能的。
用于命名存储模块、类和资源的新 URI 方案
为了解决上述三个问题,jrt
可以使用新的 URL 方案来命名运行时映像中存储的模块、类和资源,而无需透露映像的内部结构或格式。
根据RFC 3986 , URLjrt
是一个分层 URI,语法如下
jrt:/[$MODULE[/$PATH]]
其中$MODULE
是可选模块名称$PATH
,如果存在,则是该模块内特定类或资源文件的路径。 URL的含义jrt