使用 JLink 创建运行时和应用程序镜像
使用命令行工具 jlink
,您可以选择多个 模块,包括平台模块以及构成应用程序的模块,并将它们链接到运行时镜像中。此类运行时镜像就像您可以 下载的 JDK,但仅包含您选择的模块及其正常运行所需的依赖项。如果这些模块包含您的项目,则结果将是应用程序的自包含可交付成果,这意味着它不依赖于目标系统上安装的 JDK。在链接阶段,jlink
可以进一步优化镜像大小并提高 VM 性能,尤其是启动时间。
虽然对于 jlink
来说并不重要,但区分创建运行时镜像(JDK 的子集)和应用程序镜像(还包含特定于项目的模块)是有帮助的,因此我们将按此顺序进行。
注意:jlink
“仅仅”链接字节码 - 它不将其编译为机器码,因此这不是提前编译。
创建运行时镜像
要创建镜像,jlink
需要两部分信息,每部分都使用命令行选项指定
- 要开始使用的模块 /
--add-modules
- 创建镜像的文件夹 /
--output
给定这些命令行选项,jlink
解析模块,从使用 --add-modules
列出的模块开始。但它有一些特殊之处
除非遇到任何问题,例如缺少或重复的模块,否则解析后的模块(根模块加上传递依赖项)最终将出现在新的运行时镜像中。
最小的运行时
让我们看一下。最简单的运行时镜像仅包含基本模块
# create the image
$ jlink
--add-modules java.base
--output jdk-base
# use the image's java launcher to list all contained modules
$ jdk-base/bin/java --list-modules
> java.base
创建应用程序镜像
如前所述,jlink
不区分来自 JDK 的模块和其他模块,因此您可以使用类似的方法来创建包含整个应用程序的镜像,这意味着它包含应用程序模块(应用程序本身及其依赖项)以及支持它们的平台模块。要创建此类镜像,您需要
- 使用
--module-path
让jlink
知道在哪里可以找到应用程序模块 - 使用
--add-modules
指定应用程序的主模块和其他需要的模块,例如服务(见下文)或可选依赖项
总而言之,镜像包含的平台模块和应用程序模块被称为系统模块。请注意,jlink
仅对显式模块进行操作,因此依赖于 自动模块 的应用程序无法链接到镜像中。
可选模块路径
例如,假设应用程序的模块可以在文件夹 mods
中找到,并且其主模块称为 com.example.app
。然后,以下命令将在文件夹 app-image
中创建一个镜像
# create the image
$ jlink
--module-path mods
--add-modules com.example.main
--output app-image
# list contained modules
$ app-image/bin/java --list-modules
> com.example.app
# other app modules
> java.base
# other java/jdk modules
由于镜像包含整个应用程序,因此您在启动它时不需要使用模块路径
$ app-image/bin/java --module com.example.app/com.example.app.Main
虽然您不必使用模块路径,但您可以使用它。在这种情况下,系统模块将始终覆盖模块路径上同名模块 - 就好像模块路径上的模块不存在一样。因此,您无法使用模块路径替换系统模块,但可以向应用程序添加其他模块。这很可能是服务提供者,它允许您将镜像与应用程序一起发布,同时仍然允许用户轻松地在本地扩展它。
生成本地启动器
应用程序模块可以包含自定义启动器,它是在镜像的 bin
文件夹中的可执行脚本(在基于 Unix 的操作系统上为 shell,在 Windows 上为批处理),该脚本预先配置为使用具体模块和主类启动 JVM。要创建启动器,请使用 --launcher $NAME=$MODULE/$MAIN-CLASS
选项
$NAME
是您为可执行文件选择的名称$MODULE
是要启动的模块的名称$MAIN-CLASS
是模块的主类的名称
后两者是您通常放在 java --module
后面的内容。与那里一样,如果模块定义了主类,您可以省略 /$MAIN-CLASS
。
扩展上面的示例,这是创建名为 app
的启动器的方法
# create the image
$ jlink
--module-path mods
--add-modules com.example.main
--launcher app=com.example.app/com.example.app.Main
--output app-image
# launch
$ app-image/bin/app
但是,使用启动器确实有一个缺点:您尝试应用于启动 JVM 的所有选项都将被解释为如果您将它们放在 --module
选项后面,使其成为程序参数。这意味着,当使用启动器时,您无法临时配置 java
命令,例如添加我们之前讨论过的其他服务。解决此问题的一种方法是编辑脚本并将这些选项放在 JLINK_VM_OPTIONS
环境变量中。另一种方法是回退到 java
命令本身,该命令仍然可以在镜像中使用。
包含服务
为了能够创建小巧且经过精心组装的运行时镜像,jlink
默认情况下在创建镜像时不会执行任何 服务绑定。相反,服务提供者模块必须通过在 --add-modules
中列出它们来手动包含。要找出哪些模块提供特定服务,请使用选项 --suggest-providers $SERVICE
,它将列出运行时或模块路径中提供 $SERVICE
实现的所有模块。作为添加单个服务的替代方法,可以使用选项 --bind-services
来包含所有提供另一个已解析模块使用的服务的模块。
让我们以字符集(如 ISO-8859-1、UTF-8 或 UTF-16)为例。基本模块知道您日常需要使用的字符集,但有一个特定的平台模块包含其他一些字符集:jdk.charsets。基本模块和 jdk.charsets 通过服务解耦 - 以下是其模块声明的相关部分
module java.base {
uses java.nio.charset.spi.CharsetProvider;
}
module jdk.charsets {
provides java.nio.charset.spi.CharsetProvider
with sun.nio.cs.ext.ExtendedCharsets
}
当模块系统在常规启动期间解析模块时,服务绑定将拉入 jdk.charsets,因此其字符集在从标准 JDK 启动时始终可用。但是,在使用 jlink
创建运行时镜像时,默认情况下不会发生这种情况,因此此类镜像将不包含字符集模块。如果您确定需要它们,您可以简单地使用 --add-modules
将模块包含在镜像中
$ jlink
--add-modules java.base,jdk.charsets
--output jdk-charsets
$ jdk-charsets/bin/java --list-modules
> java.base
> jdk.charsets
跨操作系统生成镜像
虽然应用程序和库 JAR 中包含的字节码独立于任何操作系统 (OS),但它需要特定于操作系统的 Java 虚拟机来执行它们 - 这就是为什么您下载专门针对 Linux、macOS 或 Windows 的 JDK(例如)。由于 jlink
从中提取平台模块,因此它创建的运行时镜像和应用程序镜像始终绑定到具体的操作系统。幸运的是,它不必是您运行 jlink
的操作系统。
如果您下载并解压缩了针对不同操作系统的 JDK,则可以在运行系统 JDK 中的 jlink
版本时将它的 jmods
文件夹放在模块路径上。然后,链接器将确定要为该其他操作系统创建镜像,因此将创建一个可以在该操作系统上运行的镜像(但当然不能在其他操作系统上运行)。因此,给定所有支持应用程序的操作系统的 JDK,您可以在同一台机器上为每个操作系统生成运行时镜像或应用程序镜像。为了使此操作顺利进行,建议仅引用与 jlink
二进制文件完全相同的 JDK 版本中的模块,例如,如果 jlink
的版本为 16.0.2,请确保它加载来自 JDK 16.0.2 的平台模块。
让我们回到我们之前创建的应用程序镜像,并假设它是在 Linux 构建服务器上构建的。然后,这是为 Windows 创建应用程序镜像的方法
# download JDK for Windows and unpack into `jdk-win`
# create the image with the jlink binary from the system's JDK
# (in this example, Linux)
$ jlink
--module-path jdk-win/jmods:mods
--add-modules com.example.main
--output app-image
要验证此镜像是否特定于 Windows,请检查 app-image/bin
,其中包含 java.exe
。
优化镜像
在学习了如何为应用程序生成镜像或使用应用程序生成镜像之后,您可以优化它。大多数优化会减小镜像大小,有些会稍微提高启动时间。查看 jlink
参考,了解您可以使用的所有选项的完整列表。无论您应用了哪些选项,都不要忘记彻底测试生成的镜像并衡量实际改进。
上次审核: 2021 年 11 月 3 日
返回教程列表