跳到主要内容

JEP 493: 不使用 JMOD 文件链接运行时镜像

QWen Max 中英对照 JEP 493: Linking Run-Time Images without JMODs

概述

通过启用 jlink 工具来创建自定义运行时镜像,而无需使用 JDK 的 JMOD 文件,从而将 JDK 的大小减少约 25%。此功能必须在构建 JDK 时启用;默认情况下不会启用,并且一些 JDK 供应商可能会选择不启用它。

目标

允许用户从模块链接运行时镜像,无论这些模块是独立的 JMOD 文件模块化 JAR 文件,还是之前链接的运行时镜像的一部分。

动机

在云环境中,文件系统上安装的 JDK 的大小非常重要,因为包含已安装 JDK 的容器镜像会从容器注册表通过网络自动且频繁地复制。减小 JDK 的大小将提高这些操作的效率。

一个完整的、已安装的JDK有两个主要组成部分:运行时镜像,即可执行的Java运行时系统,以及一组打包模块,以JMOD格式为运行时镜像中的每个模块提供。

JMOD 文件在使用 jlink 工具创建自定义运行时镜像时会被用到。完整 JDK 中的运行时镜像本身就是这样一种镜像,它是通过 jlink 从这些 JMOD 文件创建的。因此,运行时镜像中的每个类文件、本地库、配置文件和其他资源也都存在于这些 JMOD 文件之一中 —— 可以说这是对空间的巨大浪费。

实际上,一个完整的 JDK 中的 JMOD 文件约占 JDK 总大小的 25%。如果我们能够增强 jlink 工具,从运行时镜像本身提取类文件、本地库、配置文件和其他资源,那么我们可以通过省略 JMOD 文件来大幅减小安装的 JDK 的大小。

描述

新的 JDK 构建时 配置选项 --enable-linkable-runtime 构建的 JDK 的 jlink 工具可以创建运行时镜像,而无需使用 JDK 的 JMOD 文件。生成的 JDK 不包括这些文件,也就是说,没有 jmods 目录。因此,它比用默认配置构建的 JDK 大约小 25%,尽管它包含完全相同的模块。

$ configure [ ... other options ... ] --enable-linkable-runtime
$ make images

jlink 工具在任何 JDK 构建中都可以使用 JMOD 文件和模块化的 JAR 文件。此外,在启用此功能的 JDK 构建中,jlink 可以使用它所属的运行时镜像中的模块。jlink--help 输出显示了它是否具有此功能:

$ jlink --help
Usage: jlink <options> --module-path <modulepath> --add-modules <module>[,<module>...]
...
Capabilities:
Linking from run-time image enabled
$

这意味着使用的 jlink 工具可以链接包含的运行时镜像中的 JDK 模块。如果它没有这个能力,那么它会显示 Linking from run-time image disabled

具有新功能的 jlink 版本总是优先从模块路径上的 JMOD 文件中使用 JDK 模块(如果可用)。它将只在其所属的运行时镜像中使用模块,前提是模块路径上找不到 java.base 模块。任何其他模块仍然必须通过 --module-path 选项指定给 jlink

使用带有新功能的 jlink 与不带该功能的 jlink 在用户体验上是完全相同的。如果我们希望通过省略某些模块来减小运行时镜像的大小,我们可以通过仅包含所需的模块来继续这样做,而不需要 JMOD 文件。例如,要创建一个仅包含 java.xmljava.base 模块的运行时镜像,jlink 的调用方式是相同的:

$ jlink --add-modules java.xml --output image
$ image/bin/java --list-modules
java.base@24
java.xml@24
$

jlink 的输出与从 JMOD 文件链接模块时完全相同。生成的运行时镜像比完整的 JDK 运行时镜像小约 60%。

更复杂的调用情况也是一样的。例如,假设我们想要创建一个包含应用程序模块 app 的运行时镜像,而该模块需要一个库 lib。这些模块被打包为 模块化 JAR 文件,存放在 mlib 目录中。我们通过 --module-path 选项将它们指定给 jlink,这和通常的做法一样:

$ ls mlib
app.jar lib.jar
$ jlink --module-path mlib --add-modules app --output app
$ app/bin/java --list-modules
app
lib
java.base@24
$

jlink 工具从模块化的 JAR 文件 app.jarlib.jar 中复制 applib 模块的类文件和资源。它从 JDK 的运行时镜像中提取 JDK 模块的类文件、本地库、配置文件和其他资源。

jlink--verbose 选项现在显示每个模块的来源:

$ ls custom-jmods
foo.jmod
$ jlink --add-modules foo \
--module-path=custom-jmods \
--verbose \
--output foo-image
Linking based on the current run-time image
java.base jrt:/java.base (run-time image)
foo file:///path/to/custom-jmods/foo.jmod

Providers:
java.base provides java.nio.file.spi.FileSystemProvider used by java.base
$

这里模块 java.base 是从当前运行时镜像中提取的,而模块 foo 则是从 JMOD 文件 foo.jmod 中链接的。

默认未启用

默认的构建配置将保持不变:生成的 JDK 将包含 JMOD 文件,其 jlink 工具在没有这些文件的情况下将无法运行。你从首选供应商那里获得的 JDK 构建是否包含此功能取决于该供应商。

我们可能会在将来的版本中默认启用此功能。

限制

在使用 --enable-linkable-runtime 选项构建的 JDK 中,jlink 工具与使用默认配置构建的 JDK 相比有以下一些限制:

  • jlink 不能用于创建一个本身包含 jlink 工具的运行时镜像。jlink 工具在 jdk.jlink 模块中,因此以下操作会失败:

    $ jlink --add-modules jdk.jlink --output image
    Error: This JDK does not contain packaged modules and cannot be used \
    to create another run-time image that includes the jdk.jlink module

    如果将来证明这个限制有问题,我们可能会重新考虑。

  • 如果任何用户可编辑的配置文件被修改,jlink 将失败。

    JDK 的 conf 目录包含各种开发人员可能编辑以配置 JDK 的文件。特别是 conf/security/java.security 文件,它配置了安全提供者、加密算法等。在默认构建中,jlink 从 JDK 的 JMOD 文件复制 JDK 模块的用户可编辑配置文件。如果没有 JMOD 文件,jlink 会从运行时镜像复制配置文件,并且如果这些文件与原始文件不同,则会失败:

    $ jlink --add-modules java.xml --output image
    Error: [...]/bin/conf/security/java.security has been modified

    这个限制防止 jlink 创建一个具有临时或不安全配置的运行时镜像。如果安全配置被更改,例如启用默认禁用的过时消息摘要算法,那么将该配置复制到新的运行时镜像是不合适的。

  • 跨平台链接,例如在 Linux/x64 上运行 jlink 以创建 Windows/x64 的运行时镜像是不可能的。

  • 从使用 --patch-module 的运行时镜像进行链接是不支持的。

  • 通过从不同的运行时镜像提取模块进行链接(例如,通过将该镜像指定给 --module-path 选项)是不支持的。

替代方案

JDK供应商可以将JDK的JMOD文件作为单独的下载提供。一些Linux发行版已经通过为JDK运行时映像提供一个安装包,为相应的JMOD文件提供另一个安装包,基本上实现了这一点。

这种方法很脆弱,因为如果未安装第二个包,则 jlink 工具将无法工作。此外,这种方法也不适合云环境,在云环境中,JDK 运行时镜像及其 JMOD 文件可能会位于不同且相互冲突的容器镜像层中。