找回密码
立即注册
搜索
发回帖 发新帖

6587

积分

0

好友

798

主题
发表于 12 小时前 | 查看: 1| 回复: 0

写在开头

同事那台机器,服务一启动就炸。

报错大概长这样:

java.lang.ExceptionInInitializerError
Caused by: com.xxx.crypto.CryptoException: Cannot load library: nativeapi

同一份代码,我们五六个人跑得好好的,就他那一台必挂。

这个 SDK 会把几个原生库解压到临时目录再加载。正常情况下 %TEMP% 底下能看到 5 个 dll。他截图给我,只有 4 个,少的那个正好就是报错点名的那个。

第一个念头太自然了:文件没生成出来。

我们照着这个方向查了将近两个小时,能试的都试了。先把 jar 包重装一遍,没用。我又把自己 Maven 仓库里的包整个拷给他,还是没用。怀疑过是他的杀毒软件把磁盘写入拦了,卸载掉重启一遍,照样没用。最后连 Maven 配置都怀疑上了,想着是不是仓库地址引错,Maven 背地里重新下了一份 jar 包。

没有一条对得上。

后来我把这个问题丢给了 WorkBuddy,一个桌面 Agent,让它带着我从代码这一侧重新找一遍。

一、报错点名的那个库,其实是无辜的

真正把我拉出来的,是一个逻辑上的矛盾。

我让 Agent 把 SDK 的加载逻辑反编译出来,一行一行看。

throw new CryptoException("Cannot load library: " + failedLibraryName);

关键在这个变量 failedLibraryName,它只在 catch 到 UnsatisfiedLinkError 的时候才会被赋值。

也就是说,程序走到抛异常这一行的时候,已经经历过一次 System.load() 了,而且失败了一次。

那文件必然是存在的。

文件不是没生成,是加载失败之后被删掉了。

二、文件为什么不见了

这个 SDK 干的事是这样的。

先把几个 dll 从 jar 里解压到 java.io.tmpdir/wcsp/<随机数字>/,再逐个 System.load(),干完活把整个临时目录删掉,删不掉就改个名标记一下。

第三步是最关键的。

Windows 上有个特性,已经映射进进程的 dll 文件是删不掉的。加载成功的库被映射了,文件删不动,留在原地。加载失败的库没被映射,可以正常删掉。

所以最后残留下来的是加载成功的那些。

缺哪个文件,恰恰说明哪个加载失败了。

这里还有半句要补。解析库这一步本身是静默的,那一组读不到就直接跳过,不抛错。所以你看到的场面是:文件没生成,日志里也什么都没有,一条线索都不给。

我在自己机器上翻了一下 %TEMP%,底下堆了二十来个 SDK 没清干净的残留目录。点开看,内容正好能对上。

五个变四个,少的就是报错里那一个。

到这里,文件没生成这条路彻底排除了。而且反过来,它还成了一条线索。

如果下次你在别人机器上撞见同类现象,有个最快的判据:去 %TEMP%\wcsp\ 底下找最新的那个数字目录,缺谁,就是谁加载失败。不用等报错,也不用翻日志。

三、五个库,为什么只挂了它一个

这是整件事里最值钱的一步。

Agent 把那几个 dll 的 PE 导入表全解析了一遍,看它们各自依赖哪些 C/C++ 运行库。

dll 依赖的运行库
报错那个 MSVCR100.dll
库 B MSVCP140 + VCRUNTIME140
库 C VCRUNTIME140
日志库 无

只有它一个依赖 MSVCR100.dll。

MSVCR100 就是 VC++ 2010 的运行库。

这里有个很多 Java 开发者不会留意的点:VC++ 2010 运行库不是 Windows 自带的。系统内置的是 2015 之后那一套,也就是 VCRUNTIME140 系列。MSVCR100 到底在不在,完全取决于这台机器历史上装过什么软件。

那有的机器正常、有的机器必挂,就说通了。这件事跟 Java 版本没关系,跟机器装过什么有关系。

同时也解释了为什么偏偏是它。其他几个库依赖的 VCRUNTIME140 是系统自带的,它们没事。

顺带还排掉了一个我很早就怀疑过的方向。我一度以为 jar 里压根没带这个库,拆开一看,带了。所以从一开始就不是打包问题。

中间还有一层,差点把我带偏。

报错那个库除了 MSVCR100,还隐式依赖同目录下的日志库。

而 System.load(绝对路径) 有个挺反直觉的行为:它不会去被加载的那个 dll 自己所在的目录里找依赖。

这个我让它写了个最小程序测过。

System.load(dir + "nativeapi.dll");   // 必抛 UnsatisfiedLinkError: Can't find dependent libraries
System.load(dir + "syslog.dll");
System.load(dir + "nativeapi.dll");   // 成功

那正常机器为什么一点事没有?

因为 SDK 内部有个重试循环。第一轮日志库排在它后面,所以它失败;第二轮日志库已经加载好了,它就成功了。

这个重试在正常机器上把顺序问题掩盖掉了。

但它掩盖不了真正缺失的依赖。如果它是因为找不到 MSVCR100 而失败,重试多少次都一样。

四、真因和修复

根因既然锁在运行库上,那就去那台机器上确认。

一看 System32 底下那个 msvcr100.dll,没有。结案。

修复很简单,装官方的运行库,32 位和 64 位都装上,省得别的老程序接着踩。

x64  https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe
x86  https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe

装完重启 Java 进程就行,不用重启机器。

要是那台机器不让装东西,应急方案是从正常机器把文件拷过去。这里一定要认位数,拷错了等于白拷。

文件 大小 位数
System32\msvcr100.dll 830,616 字节 x64
SysWOW64\msvcr100.dll 774,808 字节 x86

落点选 java.exe 同目录最省事。因为 Windows 解析依赖时的搜索顺序里,第一站就是应用程序所在目录,然后才轮到 System32。

这里有个坑要绕开。别把它放进 %TEMP%\wcsp\<数字>\ 那个目录,那目录名每次启动都是新的随机数,而且 System.load 用绝对路径的时候,不会把被加载 dll 自己的目录加进搜索路径。上面那个顺序坑就是这么来的。

顺手留几条自检命令,遇到类似的可以直接用。

:: 两个位数目录一起看,有可能只装了 x86 版
dir C:\Windows\System32\msvcr100.dll C:\Windows\SysWOW64\msvcr100.dll

:: 确认 JVM 位数,决定装 x64 还是 x86
java -XX:+PrintFlagsFinal -version 2>&1 | findstr "sun.arch.data.model"

:: 注册表权威键,返回 Installed=0x1 就是装了
reg query "HKLM\SOFTWARE\Microsoft\VisualStudio\10.0\VC\VCRedist\x64" /v Installed

还有两条,也是让它写最小程序测的,-Djava.library.path 和把 dll 目录加进 PATH,都救不回来,别再往那两个方向花时间。

写在结尾

这次真正记住的是三件事。

一,报错点名的那个东西,未必就是问题所在。先确认文件到底在不在,再决定往哪一侧查。

二,Windows 上文件凭空消失,先想想是不是加载失败之后被清掉的。已经映射进进程的 dll 删不掉,留下来的那几个反而能告诉你谁成功了。

三,Java 的报错指向系统依赖的时候,别只在 Java 这一侧找。这次最后落到的,是一条跟 Java 一点关系都没有的运行库。

这次剥到根因,AI 帮了大忙。反编译 SDK 看加载逻辑,解那几个 dll 的 PE 导入表,写最小程序复现加载失败,都是它跑的。最后把它坐实的,是那台机器上一条最朴素的命令。




上一篇:LEFT JOIN 右表条件写 ON 还是 WHERE?我一次少数据,一次重复统计
下一篇:COCOS Creator全版本更新第二批:16个版本一键发布全平台
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-18 19:26 , Processed in 0.067493 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表