使用JPackage部署带JNI依赖的OpenJDK应用遇加载问题
1. Windows不检查进程工作目录加载库的行为是否一直存在?
是的,这个行为并非新出现,是Windows从Vista开始引入的安全设计(XP SP2也有类似限制)。默认情况下,LoadLibrary函数的DLL搜索顺序不包含进程当前工作目录,标准顺序为:
- 进程主可执行文件(比如
java.exe)所在目录 - 系统目录(32位程序对应
SysWOW64,64位对应System32) - 16位系统目录(
System) - Windows目录
PATH环境变量中的所有目录
你之前的认知有误,Windows不会默认检查进程工作目录来加载依赖DLL,这是为了防范DLL劫持攻击。
2. 这真的是未解决的OpenJDK bug吗?
你提到的JDK-8213772确实是一个长期未解决的OpenJDK问题,核心是jpackage生成的启动器没有将应用的原生库目录加入进程级PATH,导致依赖DLL无法被搜索到。但你把DLL放到启动器所在目录仍无法解决的原因是:jpackage生成的启动器会调用JVM的java.exe,此时进程的主可执行文件是java.exe而非启动器,所以DLL搜索顺序的第一个目录是java.exe所在的runtime/bin目录,而非启动器的安装目录——这才是你操作后问题依旧的关键。
3. 如何部署应用而无需修改用户PATH?
有几种可行的方案,无需修改系统/用户级PATH:
临时修改进程级PATH:
自定义jpackage的启动脚本(比如用--win-console生成控制台程序,或者手动修改启动器配置),在启动JVM之前将原生库目录添加到当前进程的PATH中。比如在批处理脚本中:@echo off setlocal rem 将nativelibs目录(相对于启动脚本的路径)加到进程PATH set PATH=%~dp0nativelibs;%PATH% rem 启动JVM "%~dp0runtime\bin\java.exe" -Djava.library.path="%~dp0nativelibs" -jar "%~dp0app\your-app.jar" endlocal进程级PATH仅对当前应用进程有效,不会影响其他程序,也不会与系统中64位库冲突。
提前手动加载依赖DLL:
在Java代码中,调用第三方库之前,先用System.load()指定依赖DLL的绝对路径提前加载。比如:// 在初始化第三方库前执行 String libPath = System.getProperty("java.library.path"); System.load(libPath + File.separator + "lib1-dependency.dll"); // 再调用第三方库加载lib1.dll一旦依赖DLL被加载到进程中,后续
lib1.dll尝试加载它时会直接使用已加载的实例,无需再搜索路径。将DLL放到JVM的bin目录:
将所有DLL复制到jpackage生成的runtime/bin目录(即java.exe所在目录),这样LoadLibrary会优先搜索这个目录。缺点是会污染JVM的目录,但对于独立部署的应用来说影响不大。
4. 你哪里操作有误?
主要有两个认知偏差:
- 混淆了进程工作目录和主可执行文件所在目录:Windows搜索DLL时优先看主可执行文件(
java.exe)的目录,而非进程当前工作目录,你误以为工作目录会被搜索是错误的。 - 错误判断了启动器的作用:jpackage生成的启动器只是调用
java.exe的包装,进程的主可执行文件是java.exe,所以将DLL放到启动器所在目录无法被搜索到。
内容的提问来源于stack exchange,提问作者Jake Henry

