使用 GraalVM 将基本的 Java 项目打包成 EXE

  • GraalVM 的环境搭建
  • 将基本的 Java 项目打包成 EXE
  • 将 JAR 转化为 EXE
  • GraalVM 与 Java 中其它打包成 EXE 的方法对比
  • 提醒与补充


运行环境:

  • GraalVM Enterprise 21.3.0
  • Java 语言级别:17
  • Maven 3.8.3
  • IntelliJ IDEA 2021.2.2 (Ultimate Edition)
  • Windows 10 教育版 64位

GraalVM 的环境搭建

  1. GraalVM 有两种版本,Community 和 Enterprise。其中,前者对应于 OpenJDK,后者对应 Oracle JDK。需要根据自己的需要进行选择,本文以 GraalVM Enterprise 21.3.0 为例。
    GraalVM 版本选择网址:https://www.graalvm.org/downloads/
  2. 下载 GraalVM Enterprise 21.3.0 需要进入 Oracle 官网,网址:https://www.oracle.com/downloads/graalvm-downloads.html?selected_tab=1
  3. 先下载 Oracle GraalVM Enterprise Edition Core。下载完成之后,应该会得到一个 zip 压缩包。
  4. 再在刚才的页面下载 Oracle GraalVM Enterprise Edition Native Image。这次得到的应该是一个 JAR 包。
  5. 将前面的 zip 压缩包置入自己喜欢的文件夹下解压,解压得到的就是 GraalVM 程序文件。与安装 JDK 时类似,将含 bin 的目录作为 GraalVM 的安装目录。设环境变量如下:
  • 变量名:ORACLE_GRAALVM_HOME
    变量值:C:\Program Files\Java\graalvm-ee-java17-21.3.0
  • 变量名:GRAALVM_HOME
    变量值:%ORACLE_GRAALVM_HOME%
  • 变量名:Path
    变量值:%GRAALVM_HOME%\bin
  1. 如果上面的环境变量设置成功,在 CMD 的任意路径中输入以下命令应该能看到上面设置的路径。 where java
    where gu
C:\>where java
C:\Program Files\Java\graalvm-ee-java17-21.3.0\bin\java.exe

C:\>where gu
C:\Program Files\Java\graalvm-ee-java17-21.3.0\bin\gu.cmd
  1. 安装 Native Image 包。Native Image 包就是前面下载的 JAR 包。输入以下命令对 Native Image 进行本地安装。 gu -L install Native Image 的 JAR 包路径
    其中,Native Image 的 JAR 包路径 要替换成实际的路径。
  2. GraalVM 的运行需要 Visual Studio 中的 MSVS 的支持,因此需要下载 Visual Studio。编写本博客时,笔者下载的是 Microsoft Visual Studio Enterprise 2022 (64 位)
    Visual Studio 下载网址:https://visualstudio.microsoft.com/zh-hans/vs/
  3. 现在,Visual Studio 在官网上只会提供在线安装包。下载完在线安装包后,选择安装含 MSVS 的选项。
  4. 至此,GraalVM 的运行环境已经搭成。下面将开始使用 GraalVM 进行打包。

将基本的 Java 项目打包成 EXE

  1. 建一个简单的 Java 项目。此项目的路径中不能有中文,否则后面 GraalVM 运行时会报错。
package demo;

public class OriginJava {
    public static void main(String[] args) {
        System.out.println("Hello, world.");
    }
}
  1. 制作 .class 文件,因为 GraalVM 只能直接对 .class 文件进行操作。
  2. 制作 .class 文件的方法有很多,这里只举一例。借助于 Java 命令 javac。先打开 CMD 进入上述 Java 项目的包的根目录,也就是文件夹 demo 所在的目录。然后输入如下命令制作 .class 文件。 javac 包名/类名

java中Groovy最方便 java graalvm_java

java中Groovy最方便 java graalvm_java中Groovy最方便_02

  1. 输入如下命令使用 MSVS 的环境。这个命令是用于设置临时设置与 MSVS 相关的环境变量,因此每次总是要输。 call "vcvars64.bat 的路径"

  1. 【踩坑提醒】
      如果不输入以上命令,后面运行 native-image 时会有如下报错:
Error: Default native-compiler executable 'cl.exe' not found via environment variable PATH
Error: To prevent native-toolchain checking provide command-line option -H:-CheckToolchain
Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
[demo.hello:10212]      [total]:   3,021.75 ms,  0.96 GB
# Printing build artifacts to: D:\XXX.txt
Error: Image build request failed with exit status 1
  1. vcvars64.bat 提供了 MSVS 的运行环境。虽然也可以通过设置 MSVS 的环境变量来代替,不过这样要设置的环境变量会有很多。对于笔者的 Microsoft Visual Studio Enterprise 2022 (64 位),以上的命令为: call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
D:\OriginJava\src>call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.0.4
** Copyright (c) 2021 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
  1. .class 的包的根路径中输入以下命令制作 EXE。 native-image 包名.类名
D:\OriginJava\src>native-image demo.OriginJava
[demo.originjava:4128]    classlist:   1,950.10 ms,  0.96 GB
[demo.originjava:4128]        (cap):   1,737.90 ms,  0.96 GB
[demo.originjava:4128]        setup:   4,282.33 ms,  0.96 GB
[demo.originjava:4128]     (clinit):     162.30 ms,  2.34 GB
[demo.originjava:4128]   (typeflow):   2,867.26 ms,  2.34 GB
[demo.originjava:4128]    (objects):   5,915.14 ms,  2.34 GB
[demo.originjava:4128]   (features):   1,771.20 ms,  2.34 GB
[demo.originjava:4128]     analysis:  11,311.80 ms,  2.34 GB
[demo.originjava:4128]     universe:   1,424.64 ms,  2.35 GB
[demo.originjava:4128]      (parse):     711.20 ms,  2.35 GB
[demo.originjava:4128]     (inline):   1,436.68 ms,  2.33 GB
[demo.originjava:4128]    (compile):  19,422.42 ms,  2.82 GB
[demo.originjava:4128]      compile:  22,571.05 ms,  2.82 GB
[demo.originjava:4128]        image:   1,395.47 ms,  2.82 GB
[demo.originjava:4128]        write:     753.49 ms,  2.82 GB
[demo.originjava:4128]      [total]:  43,933.93 ms,  2.82 GB
# Printing build artifacts to: D:\OriginJava\src\demo.originjava.build_artifacts.txt

java中Groovy最方便 java graalvm_java中Groovy最方便_03

  1. 可以看出,当前目录下已经生成了一个 EXE 文件,可直接运行。

将 JAR 转化为 EXE

  1. GraalVM 还可以将 JAR 转化为 EXE。
  2. 使用如下命令可以将 JAR 转化为 EXE。但是,这样转化出来的 EXE 仍然依赖于 JDK,所以一般意义不大。 native-image -jar JAR 包的路径
  3. 使用如下命令可以产生无 JDK 依赖的 EXE。 native-image -jar -no-fallback JAR 包的路径

GraalVM 与 Java 中其它打包成 EXE 的方法对比

  但是相对于 Java 中其它打包成 EXE 的方法,如果 GraalVM 打包成功,GraalVM 可以说是最优的方式。 Java 中其它打包成 EXE 的方式无非就如下几种,或者是它们的变体。

  • 方式 1:先生成 JAR,然后将运行 JAR 包的命令写在一个脚本中,以后直接运行脚本即可。
    但此方法本质上是先运行 Java 虚拟机,然后再用 Java 虚拟机加载运行 Java 代码。Java 虚拟机会占用较高的内存,且在这种方式下,Java 项目无法拥有自己的父进程名,对外只显示为 Java 虚拟机在运行。
  • 方式 2:底层原理同方式 1,但将上面的脚本封装成了一个 EXE。
    这样做能让普通用户不能直接操作底层的脚本命令,提高了一定的安全系数和用户的友好度。除此之外,它跟方式 1 没有太大的区别。但根据使用的封装软件不同,此 Java 项目可能拥有自己的进程名与图标。
  • 方式 3:底层原理同方式 2,但使用了一种 EXE 压缩软件将上面的 JAR 包与脚本封装成了一个 EXE。
  • 方式 4:底层原理同方式 3,但这种方式将 JRE 的相对路径信息也储存到了 EXE 中,这样可以直接在此 EXE 附近放一个 JRE 文件,以后就可以在新电脑上无需先安装 Java 即可马上运行。
    但这种方式需要向用户提供 JRE,JRE 的体积一般都非常庞大。另外,这种方式本质上与前面几种方式一样,也是先运行 Java 虚拟机,然后再用 Java 虚拟机加载运行 Java 代码。而 Java 虚拟机会占用较高的内存。
  • 方式 5:前期步骤同方式 4,但额外借助了 Java 的一些内置工具命令精简了 JRE 体积,使得打包之后应用体积大大减少。
    但这种方式需要每次用人力去排除和筛选 JRE,然后编写命令执行,无一般的规律可寻,且误操作之后可能导致打包后运行失败,这会增加人力时间成本。另外,这种方式只是减少了打包之后应用体积,而没有减少运行内存。
  • 方式 6:这种方式不同前面的这几种方式。它直接使用 Java 内置的打包命令即可生成 EXE,而无需借助第三方制 EXE 的软件。制好后可在新电脑上无需先安装 Java 即可马上运行。
    但这种方式需要每次用人力进行文件布局和多次编写命令执行。另外,这种方式打包后的应用,其运行时本质上还是依赖于 Java 虚拟机。
  • 方式 7:底层原理同方式 6,但它被嵌入到 Java 的一种构建工具(如 Maven)的插件中,一定程序可以减少手动配置并适合离不开构建工具的项目。除此之外,它跟方式 6 没有太大的区别。
  • GraalVM 方式:这种方式不同前面的这几种方式。它生成的 EXE 的运行无需借助 Java 虚拟机,更无需先安装 Java,且无论是应用体积还是运行内存在所有的打包方式是都是最少的
    但它不能直接支持含 Java 反射、动态代理(如 CGLIB)的 Java 项目,需要每次用人力统计所有使用过 Java 反射、动态代理的类,无一般的规律可寻。另外,在所有的打包方式中,GraalVM 打包的耗时最长,CPU、内存消耗都是最大的。

提醒与补充

  • 本文只是给出使用 GraalVM 打包 Java 项目的基本方法。由于 GraalVM 使用的是 AOT 技术,所以它不能直接用于打包含 Java 反射、动态代理的 Java 项目。一般来说,如果想对含 Java 反射、动态代理的 Java 项目使用 GraalVM 进行打包,必须提前向 GraalVM 提供一个列出了涉及 Java 反射、动态代理所有的类的 JSON 列表,否则,要么打包时会失败,要么打包后的应用在运行时会失败。
  • 对于纯 JavaFX 项目的 GraalVM 打包,可见笔者的另一篇博客:
    使用 GraalVM 将纯 JavaFX 项目打包成 EXE: