Skip to content
欢迎扫码关注公众号

JEP 493: Linking Run-Time Images without JMODs | 不使用 JMOD 链接运行时镜像

摘要

通过启用 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%,尽管它包含完全相同的模块。

bash
$ configure [ ... 其他选项 ... ] --enable-linkable-runtime
$ make images

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

bash
$ 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 调用是相同的:

bash
$ 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

bash
$ 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 选项现在显示每个模块的来源:

bash
$ 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 模块中,因此以下操作会失败:

    bash
    $ jlink --add-modules jdk.jlink --output image
    错误:此 JDK 不包含打包的模块,并且无法用于创建包含 jdk.jlink 模块的另一个运行时镜像

    如果这被证明存在问题,我们将来可能会重新审视这一限制。

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

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

    bash
    $ jlink --add-modules java.xml --output image
    错误:[...]/bin/conf/security/java.security已被修改

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

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

  • 来自本身使用 --patch-module 选项的运行时镜像进行链接是不受支持的。

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

替代方案

JDK 供应商可以单独提供 JDK 的 JMOD 文件作为独立下载。一些 Linux 发行版已经基本上做到了这一点,它们为 JDK 运行时镜像和相应的 JMOD 文件分别提供了安装包。

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