使用jlink交叉编译实现最小的JRE - Jake

23-01-24 banq

jlink是一个 JDK 工具,用于为您的应用程序创建定制的最小 JRE。让我们用“Hello, world!”来试试吧 程序:

class Main {
  public static void main(String... args) {
    System.out.println("Hello, world!");
  }
}

我的笔记本电脑是 M1 Mac,我已经为其下载了 Azul Zulu JDK 19 版本。使用 JDK,我既可以编译 Java,又可以运行生成的程序。

$ mkdir out
$ zulu19.30.11-ca-jdk19.0.1-macosx_aarch64/bin/javac -d out in/Main.java
$ zulu19.30.11-ca-jdk19.0.1-macosx_aarch64/bin/java -cp out Main
Hello, world!

Azul Zulu 还提供了一个 JRE,我可以使用它来运行已编译的程序。

$ zulu19.30.11-ca-jre19.0.1-macosx_aarch64/bin/java -cp out Main
Hello, world!

请注意文件夹名称的细微变化(“jdk”→“jre”)。

如果我们将其发送给最终用户,那么二进制大小将很容易获胜。

$ du -hs zulu*
329M    zulu19.30.11-ca-jdk19.0.1-macosx_aarch64
136M    zulu19.30.11-ca-jre19.0.1-macosx_aarch64

但是 136MiB 只是打印几个字“Hello, world”?


值得庆幸的是,jlink它可以帮助我们构建一个只包含我们需要的最小 JRE。给定我们的程序,一个同级工具jdeps列出了所需的 Java 模块。

$ zulu19.30.11-ca-jdk19.0.1-macosx_aarch64/bin/jdeps \
      --print-module-deps \
      out/Main.class


我们的程序非常简单,只需要“基础”模块。现在jlink我们可以生成最小的 JRE。

$ zulu19.30.11-ca-jdk19.0.1-macosx_aarch64/bin/jlink \
      --compress 2 \
      --strip-debug \
      --no-header-files \
      --no-man-pages \
      --output zulu-hello-jre \
      --add-modules java.base

$ du -hs zulu*
 28M    zulu-hello-jre
329M    zulu19.30.11-ca-jdk19.0.1-macosx_aarch64
136M    zulu19.30.11-ca-jre19.0.1-macosx_aarch64


28MiB 不会赢得任何语言战争,但它比完整的 JRE 节省了 80%。

$ zulu-hello-jre/bin/java -cp out Main
Hello, world!


我们可以将它运送给我们的客户并收工,对吧?

$ tar -czf hello.tgz zulu-hello-jre out

$ scp hello.tgz jw@server:
hello.tgz            100%   14MB   2.0MB/s   00:07

$ ssh jw@server "tar xzf hello.tgz && zulu-hello-jre/bin/java -cp out Main"
bash: zulu-hello-jre/bin/java: cannot execute binary file: Exec format error


不!虽然我们编译的 Java 字节码是独立于平台的,但 JRE 是特定于每个平台的,而且我的服务器运行 Linux x64。

值得庆幸的是,jlink可以在不同平台的 JDK 上运行。让我们下载 Linux x64 JDK 并jlink使用--module-path.

$ zulu19.30.11-ca-jdk19.0.1-macosx_aarch64/bin/jlink \
      --compress 2 \
      --strip-debug \
      --no-header-files \
      --no-man-pages \
      --output zulu-hello-jre-linux-x64 \
      --module-path zulu19.30.11-ca-jdk19.0.1-linux_x64/jmods
      --add-modules java.base

$ du -hs zulu*
 28M    zulu-hello-jre
 36M    zulu-hello-jre-linux-x64
338M    zulu19.30.11-ca-jdk19.0.1-linux_x64
329M    zulu19.30.11-ca-jdk19.0.1-macosx_aarch64
136M    zulu19.30.11-ca-jre19.0.1-macosx_aarch64


Linux x64 JRE 比我的 ARM Mac 大一点,但与全尺寸 JRE 相比仍然小。它对客户端有效吗?

$ tar -czf hello-linux.tgz zulu-hello-jre-linux-x64 out

$ scp hello-linux.tgz jw@server:
hello.tgz            100%   16MB   2.1MB/s   00:08

$ ssh jw@server "tar xzf hello-linux.tgz && zulu-hello-jre-linux-x64/bin/java -cp out Main"
Hello, world!


有用!现在我们可以为任何平台的任何体系结构获取 JDK,并使用我们的主机jlink为每个目标有效地交叉编译最小的 JRE。

对于多架构 Docker 容器、JetBrains Compose UI 等桌面客户端、运送到无法安装完整 JDK 的设备等,这是一个很好的解决方案。