ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GraalVM 17在Windows x64下打包Java原生exe实战

GraalVM 17在Windows x64下打包Java原生exe实战 简介面向 Windows x64 开发者的 GraalVM JDK 17 二进制发行包解决的是在 Java 17 长期支持版本上兼顾稳定安全更新、多语言扩展和原生镜像加速的问题适合中高级 Java 工程师、微服务与云原生应用研发者。压缩包共包含 586 个文件整体约 305.58MB除了 dll、jmod、jar、exe 等核心运行与构建组件外还附有 license、copyright、properties、cmd 等许可证、版权声明、配置项和命令行工具由于是 zip 格式解压后设置 JAVA_HOME 与 GRAALVM_HOME 即可直接使用。该资源已有 501 人学习。JDK 17 自带密封类、记录类、开关表达式、文本块等语法特性可减少重复样板代码、提高代码可读性配合 GraalVM 的 Native Image、Truffle 框架以及高性能 JIT 编译器还能将应用编译为不依赖虚拟机的本地可执行文件或在同一运行时内调用 JavaScript、Python、Ruby 等语言从而降低启动时间与内存占用适合构建高性能微服务、多语言混合项目和云原生应用也为深入理解 Java 编译与执行机制提供了实用样例。 先说一个我自己踩过的实坑辛辛苦苦用 Java 17 写了个内部工具交付的时候客户说“你帮我装个 JDK 呗”我当时就愣了——一个不到 2MB 的小程序凭什么让人装一个 200MB 的运行环境后来我尝试把项目迁移到 GraalVM JDK 17配合 Native Image 直接编译成 Windows x64 的原生 exe双击就能跑内存占用直接砍半启动速度从秒级降到毫秒级。那一刻我才明白Java 生态里“一次编译到处运行”的另一面其实一直藏着一个“一次编译到处打包”的答案。这篇内容就是围绕graalvm-jdk-17-windows-x64-bin这条线索把我在 Windows x64 上使用 GraalVM 17 的完整经历写出来包括下载、安装、环境配置、Native Image 编译成 exe、以及各种报错的排查过程。不管你是第一次接触 GraalVM 的新手还是已经尝过 Native Image 的苦头但没完全解决的老手这篇都值得往下看。1. 从绿巨人JDK说起GraalVM 17 到底解决了什么问题1.1 GraalVM 不是另一个 JVM而是一个完整的 JDK 发行版很多人第一次听到 GraalVM以为是某个实验性的 JVM 替代品其实它是一个完整的 JDK 发行版底层基于 OpenJDK但在 JIT 编译器层面用自研的 Graal 编译器替代了传统的 C2 编译器同时额外提供了一个叫 Native Image 的 AOT 编译工具。这句话怎么理解普通 JDK 的 Java 程序是“先编译成字节码运行时再解释或 JIT 编译”而 Native Image 是“在构建阶段直接把字节码编译成机器码”生成的是一个不依赖 JVM 的原生可执行文件。在 Windows x64 上这个文件就是一个.exe。对于标题里的windows-x64-bin来说核心价值就是在 Windows 64 位环境里拿到一个可以直接运行的二进制文件而不是一堆.class文件加上一个java -cp命令。1.2 为什么是 JDK 17而不是 8、11 或者 21我选择 JDK 17 的理由很简单它是目前 Java 生态里最稳的 LTS 版本之一。虽然 JDK 21 也已经发布且 GraalVM 对其支持更好但很多第三方库、框架、甚至 IDE 插件对 JDK 17 的适配最成熟网上能搜到的解决方案也最多。更重要的是JDK 17 在性能上比 11 有明显提升比如新增了 sealed class、switch 模式匹配等语法特性而且 GraalVM Native Image 在 JDK 17 上的使用文档和社区案例非常丰富。如果你在团队协作团队里其他人用的还是 JDK 17你构建出来的东西也不会因为语言特性差异造成问题。有一个容易忽略的点GraalVM 的版本号并不完全等同于 JDK 版本号。比如graalvm-jdk-17表示的是基于 JDK 17 的 GraalVM 发行版而不是 GraalVM 编译器版本是 17。下载时一定要看清。1.3 它具体解决了什么痛点我在实际项目里感受到的痛点有三个启动速度慢传统 Spring Boot 应用在 Windows 上启动普遍要 3~8 秒而 Native Image 编译出来的原生 exe 可以在 100ms 内完成启动。对于命令行工具、定时任务、边缘计算场景这个差异是决定性的。内存占用高JVM 的堆内存、元空间、JIT 编译器自身的内存开销都不小。同样的业务逻辑原生镜像的内存占用通常只有 JVM 模式的 1/3 左右。分发困难给客户部署一个 Java 应用要么要求对方装 JDK要么用 jpackage 打一个包含运行时的大安装包。Native Image 直接给一个 exe 文件双击即用不需要任何环境依赖。如果你只是写 Web 后端部署在云端 Linux 服务器上可能感受不深但如果你是做桌面工具、内部小系统、或 To B 交付这几个痛点足以让你认真考虑 GraalVM。2. 下载与安装别只看默认版本三个选择要先想清楚2.1 从哪里下载以及社区版还是 Oracle 版GraalVM 的官方下载渠道有两个Oracle 官网和 GitHub Releases。建议优先从 GitHub Releases 下载因为文件列表清晰历史版本可追溯下载速度在多数网络环境下也可接受。针对windows-x64-bin你需要下载的文件名类似graalvm-community-jdk-17.0.9_windows-x64_bin.zip这里有个容易搞混的点文件名里带windows-x64说明这是 64 位 Windows 版本。如果你的生产环境跑在 32 位系统上我劝你直接放弃 GraalVM——官方已经不再提供 32 位 Windows 版本。这年头还在用 32 位 Windows 的场景建议先升级系统。版本选择上建议下载 Community 版社区版也就是基于 OpenJDK 的免费版本。Oracle 版与社区版的区别主要在于一些企业级支持和部分高级特性比如 Native Image 的某些优化对绝大多数开发者来说社区版完全够用。2.2 环境变量配置JAVA_HOME 别乱指下载完成后解压到一个不含空格和中文的目录比如D:\dev\graalvm-jdk-17。然后配置环境变量JAVA_HOME指向D:\dev\graalvm-jdk-17Path中添加%JAVA_HOME%\bin如果你之前装过其他 JDK一定把原来的 JAVA_HOME 改过来不然命令行里java -version显示的还是旧版本。还有一个值得单独设置的环境变量是GRAALVM_HOME很多构建工具比如 Maven 插件、IDEA 插件会显式读取它。设置方法和 JAVA_HOME 一样值为同一个目录。配置完成后在 cmd 或 PowerShell 里验证java -version看到类似这样的输出就说明环境变量生效了openjdk version 17.0.9 2023-10-17 OpenJDK Runtime Environment GraalVM CE 17.0.99.1 (build 17.0.99-jvmci-23.0-b18) OpenJDK 64-Bit Server VM GraalVM CE 17.0.99.1 (build 17.0.99-jvmci-23.0-b18)注意输出里必须有GraalVM字样否则说明你调用的还是普通 JDK。2.3 安装 native-image 组件新老版本操作不同在 GraalVM 早期的 17 版本里native-image命令需要单独安装gu install native-image但在较新的社区版里native-image工具已经随着 JDK 一起内置了。你可以直接验证native-image --version如果提示找不到命令回到上一步执行gu install native-image。gu是 GraalVM Updater 命令在bin目录下。这里我踩过一个很实际的坑用了 IDEA 自带的终端环境变量虽然改好了但 IDEA 不会自动刷新系统环境变量必须重启 IDEA 甚至重启系统才能让native-image --version生效。如果你在终端里执行命令找不到别急着怀疑安装过程先重启终端和 IDE 再说。3. 实战把 Java 应用打包成原生 exeNative Image 实战3.1 先从最简单的无依赖程序开始为了快速验证编译链路是不是通的我建议先写一个Hello.javapublic class Hello { public static void main(String[] args) { System.out.println(Hello, GraalVM Native!); } }编译并生成哦原生镜像javac Hello.java native-image Hello等待几秒钟后当前目录会生成一个Hello.exe。直接双击或命令行执行Hello.exe你会看到输出Hello, GraalVM Native!就这么简单。这个 exe 不依赖任何 JRE你把它拷到一台没有 Java 环境的 Windows x64 机器上照样能跑。这就是bin文件分发最大的魅力。不过这个文件体积可能出乎你意料——一个 Hello World 就有 10MB 左右。原因是 Native Image 会把运行时所需的类、垃圾回收器、线程管理等基础组件全部静态链接进产物。体积换来的是启动速度和独立性。3.2 带依赖的项目怎么处理以 Maven 为例实际项目不可能没有第三方库而 Native Image 这时候就开始“叛逆”了。以 Maven 项目为例你需要用到native-maven-plugin。在pom.xml里加上plugin groupIdorg.graalvm.buildtools/groupId artifactIdnative-maven-plugin/artifactId version0.9.28/version configuration mainClasscom.example.Main/mainClass imageNamemyapp/imageName buildArgs buildArg--no-fallback/buildArg buildArg--enable-url-protocolshttp/buildArg /buildArgs /configuration /plugin然后在项目根目录执行mvn -Pnative native:compile编译完成后target目录下会生成myapp.exe。如果项目里用了反射、动态代理、JNI 等特性编译过程大概率会报错或运行时报错原因在于 Native Image 需要静态分析所有可达的代码而反射目标是运行时才确定的它无法扫描到。这时候需要提供配置文件。GraalVM 提供了一个用于生成配置的工具利用 agent 模式运行应用来收集反射和资源访问信息java -agentlib:native-image-agentconfig-output-dirsrc/main/resources/META-INF/native-image .\target\classes\com.example.Main运行完成后会在指定目录生成reflect-config.json、resource-config.json、proxy-config.json等文件把这些文件放进资源目录后再重新执行mvn -Pnative native:compile。这里强调一句即使配置齐全也不代表所有框架都能完美支持 Native Image。比如经典的 Spring Boot 虽然从 3.0 开始原生支持 GraalVM但如果你用的是 Spring Cloud 全家桶、或依赖了某些需要大量反射的旧库仍然可能遇到各种诡异问题。3.3 可执行文件还能附加图标和版本信息用 Native Image 生成的 exe 默认没有图标看起来像个命令行程序。想加图标的话可以在native-image命令加-H:Namemyapp -H:Pathdist来指定产物目录然后用rcedit工具修改 exe 资源文件。这个工具是 Electron 社区常用的也能处理普通 exercedit.exe myapp.exe --set-icon app.ico --set-version-string ProductName MyApp不过要注意这个操作是改 exe 的 PE 资源段不影响程序运行逻辑。如果你不需要做安装程序只是给自己人用这一步可以省略。4. 排查实录tools.jar 报错、bin 文件、x64/x86 的那些事4.1 IDEA 报 “cannot determine path to tools.jar library for 17” 的根因热搜词里那条 “cannot determine path to tools.jar library for 17 (d:/app/java/jdk-17) ide” 我印象太深了因为这就是我第一次在 IDEA 里切到 GraalVM JDK 17 时遇到的报错。这个报错的字面意思是IDE 在 JDK 17 安装路径下找不到tools.jar但这个 jar 文件在 JDK 9 之前是存在的位于 JDK 根目录的lib/tools.jar下。JDK 9 开始模块化之后tools.jar被拆分成多个模块不再以一个独立 jar 文件形式存在。所以任何 IDE 插件、构建工具如果在代码里硬编码查找tools.jar在 JDK 17 上就会报错。解决思路分两步确保 IDEA 里 Project SDK 选择了 GraalVM 17而不是某一个残留的 JDK 8 路径。步骤是File Project Structure Project SDK点Add SDK选择 GraalVM 安装目录。如果报错仍然存在大概率是某个插件比如低版本的 Kotlin 插件、IDE 增强工具还在递归查找 tools.jar。这时候可以检查Help Show Log in Explorer看堆栈里是哪个插件触发的更新它即可。我在排查时还发现一个骚操作有些人在 IDEA 安装目录的bin下新建了一个tools.jar空文件来“骗过”检查这纯粹是自欺欺人没有任何作用还可能引发更奇怪的误判。正确做法是找到真正依赖tools.jar的插件并升级。4.2 bin 到底指什么从构建产物到文件夹命名的误区标题里这个bin可以有多个层面的理解。最常见的就是%JAVA_HOME%\bin这个可执行文件目录这不用多说。另一个层面是 Native Image 生成的二进制可执行文件。在 Linux 和 macOS 上native-image生成的文件没有扩展名只是存在bin目录里所以在很多教程里你看到的是./app而不是app.exe。而在 Windows 上它会自动生成.exe后缀。这就导致了一个容易混淆的场景同一个 Maven 项目的 protobuf 或者配置文件里写死了bin路径在 Windows 上用到了会因为在bin目录下找不到.exe而失败。还有一个层面是很多软件安装包在bin目录下同时放置了不同架构的文件比如win32/jvm.dll和win64/jvm.dll。你在下载 JDK 时一定要分清楚 x86 和 x64。graalvm-jdk-17-windows-x64-bin这个名字已经明确说明是 64 位但一些老脚本可能默认找Program Files (x86)目录导致加载jvm.dll失败报错形如 “Cant load AMD 64-bit .dll on a IA 32-bit platform”。遇到这种报错直接检查进程位数和 JDK 位数是否一致即可用命令行确认java -version | findstr 64-Bit如果没有输出64-Bit说明当前 JDK 是 32 位的换成 x64 版本就好。4.3 bin 文件相关的常见操作我们再看看热搜词里的 “bin 文件怎么打开”、“keil 生成 bin 文件”、“bin 转 xml 工具”。这些大多来自嵌入式领域bin 文件泛指固件或者二进制数据文件和 Java 的 bin 目录完全不是一回事。但在分发 GraalVM 原生 exe 的过程中有一种情况会重新牵涉到这个“bin 文件”当你希望把原生 exe 作为资源打包进另一个安装程序时这些.exe本来就是二进制数据可以通过自定义脚本做二进制拼接、版本比对。比如你在部署脚本里要比较两个原生 exe 是否一致直接fc /b app1.exe app2.exe对比二进制内容不要再想着转 XML 或者打开看到源码——原生可执行文件是机器码不是文本。我自己的习惯是将 Native Image 输出的.exe放进公司内部的制品库和普通文件一样管理。只是要注意品清单和版本号一定要在 CI 脚本里自动写入否则你根本不知道这个 exe 对应的源码 commit 是哪一次。5. 用起来才知道的细节启动速度、内存、体积以及何时别用 GraalVM5.1 实测数据同样的代码JVM 和原生镜像差距多大我说一个自己机器的实测结果仅供参考一段模拟数据批处理逻辑输入 10 万条记录做分组聚合。指标JVM 模式JDK 17Native ImageWindows x64 exe启动到 main 执行完毕约 1200ms约 80ms空闲内存占用约 220MB约 70MB处理 10 万条记录总耗时约 2100ms约 2300ms产物体积项目 jar 约 18MB原生 exe 约 52MB从这个表能看出一个很有意思的现象启动和内存优势巨大但纯计算性能反而略逊一线。原因是原生镜像的垃圾回收器和 JIT 编译在启动阶段就已经固化运行期不会再做热点优化。如果你追求的是复杂算法场景的极限吞吐JVM 模式反而可能更好。所以我的建议是对于 CLI 工具、定时任务、网关代理、边缘设备上跑的 Java 程序优先用 Native Image对于需要长时间高并发计算的中间件先做基准测试再决定。5.2 体积膨胀和压缩方案一个 Spring Boot 应用打完原生镜像体积大概率在 70MB~120MB。作为对比一个 Go 编译出来的 HTTP 服务可能只有 20MB。这也是很多人在评估时犹豫的原因。压缩可以用 UPX一个通用可执行文件压缩器upx --best myapp.exe实测压缩率大约 40%~50%能把 70MB 的 exe 压到 35MB 左右。不过压缩后的 exe 首次启动会有短暂解压过程且某些杀毒软件会误报。如果是要交付给外部客户不建议压缩安全性和兼容性优先。还有一个更明智的方向拆分产物结构。把变化频繁的配置和资源文件放外层exe 固定不动后续升级只替换资源文件这样既减小单次包体又减少完整产物体积。5.3 什么时候别用 GraalVM我的三点避坑建议第一反射用得极其泛滥的旧系统。某些老框架用反射读取类名、方法名做依赖注入即使有 agent 配置文件也总会漏掉一些运行时动态生成的类排查成本极高。这种项目还是继续用传统 JVM 更靠谱。第二需要动态加载外部 jar 的应用。Native Image 不支持在运行时随意扩展类路径如果你设计了一个插件系统允许第三方上传 jar 到目录里动态加载那么原生镜像的直接可执行文件方案基本走不通。第三原生镜像的构建速度非常慢。即使是一个简单的 Spring Boot 项目全量编译可能需要 5~10 分钟而 JVM 模式下打包只需要几十秒。如果你是本地开发反复调试建议日常还是用 JVM 模式跑只在发版时用 Native Image 构建。5.4 运行时和分发策略的最终建议如果你最终选择 GraalVM 17 作为构建基础设施我建议你坚持一套完整的 CI 流程推送代码到仓库后先执行 JVM 模式的测试套件保证业务逻辑正确。使用 agent 模式收集反射配置提交到资源目录。打 tag 时触发 Native Image 构建产物上传至制品库。下载产物到 Windows 10/11 x64 干净环境双击运行冒烟测试。这套流程我坚持了半年再也没出现过给客户发过去一个点不开的 exe 的尴尬局面。过程中还发现一个小技巧构建机器尽量和客户机器保持同一 Windows 大版本比如都在 Win10 21H2 上构建避免因为系统 API 差异导致在客户机器上报缺失 DLL。根据我个人的经验GraalVM JDK 17 在 Windows x64 上的使用难度被很多人高估了。真正的门槛不在安装、不在构建而在于你是否愿意花时间去理解反射配置和构建期静态分析的限制。一旦跨过这道坎你得到的不仅是graalvm-jdk-17-windows-x64-bin这个目录名而是一套不受运行时环境掣肘的交付方式。最后再分享一个收尾小技巧如果你在多个项目间频繁切换普通 JDK 和 GraalVM建议在 Windows 上用bat脚本做环境变量切换而不是每次手动改系统设置。写两个简单的脚本use-graal.bat设置JAVA_HOME指向 GraalVMuse-jdk.bat指回普通 JDK再开一个新终端窗口生效。这个习惯帮我少走了很多弯路。本文还有配套的精品资源点击获取
返回列表