用 exe4j 把一个 JavaFX 桌面程序打成 exe,自带 JRE 一起发出去,结果双击 exe 直接弹「找不到 jvm」之类的错。机器上明明装了 Java,自带的 JRE 目录也都在,就是起不来。

折腾一圈发现是 exe4j 找 JRE 的路径没配对。

exe4j 打出来的 exe 本质是个启动器,它自己不包含 JVM,运行的时候要先在机器上找一个可用的 JRE 再拉起真正的程序。它找 JRE 是按一个「搜索顺序」来的,默认会先去找系统注册的 Java、JAVA_HOME 之类,找不到才往下走。问题就出在这:开发机上系统 Java 是高版本的,能蒙混过去;到了用户那台干净机器上,啥系统 Java 都没有,它又没被告诉「去我自带的那个 ./jre 里找」,自然就报找不到。

修法是在 exe4j 的配置里显式给一个搜索路径,优先指向打包进去的那个 JRE 目录。exe4j 的配置是份 xml,对应的是 <searchSequence> 这一段:

<searchSequence>
<directory location="./jre" />
</searchSequence>

就是把「相对于 exe 所在目录的 ./jre」放到搜索序列的最前面。打包的时候把精简过的 JRE(我用 jlink 裁了一份只含要用的模块的)整个放到发版目录的 jre/ 下,exe 起来就先从这儿找,稳稳命中自带的那份,跟用户机器上装没装 Java 彻底脱钩。

有个坑顺带说一下:这个 JRE 目录名得和配置里写的对得上,打包脚本挪文件的时候别把 jre 改名或者放到别的层级,否则 ./jre 这个相对路径就指空了,又是同样找不到的报错。我用 Gradle task 自动生成这份 exe4j 配置 + 拷 JRE,两步用同一个目录变量串起来,省得手抖对不上。

其实这类「在我这好好的、到你那不行」的问题,根子基本都是把开发机上的环境当成了默认环境。启动器找 JRE 这种事,别指望它自己去猜,显式把自带 JRE 的路径塞到搜索序列最前面,一份自包含的 exe 才算真的能到处跑。