1 环境
os : window 10
jdk : JDK-8u331
SpringBoot: 2.0.
2 现象
一个 SpringBoot 的项目, 使用了 undertown 作为 web 容器的项目。
项目启动成功后, 第一次发起 http 请求接口, 任意一个接口都可以, 会直接抛出如下异常:
nested exception is : : Invalid file path
: Handler dispatch failed; nested exception is : : Invalid file path
at (:982)
at (:901)
at (:970)
at (:872)
at (:707)
at (:846)
at (:790)
at (:85)
at $(:129)
at (:55)
at (:107)
at (:61)
at $(:131)
at (:110)
at (:107)
at (:61)
at $(:131)
at com.(:116)
at (:61)
at $(:131)
at com.(:153)
at (:61)
at $(:131)
at (:197)
at (:107)
at (:61)
at $(:131)
at (:106)
at (:107)
at (:61)
at $(:131)
at (:84)
at (:62)
at (:36)
at (:131)
at (:57)
at (:43)
at (:46)
at (:64)
at (:60)
at (:77)
at (:43)
at (:43)
at (:43)
at (:292)
at $100(:81)
at $(:138)
at $(:135)
at $(:48)
at $(:43)
at (:272)
at $000(:81)
at $(:104)
at (:211)
at $(:809)
at (Unknown Source)
at $(Unknown Source)
at (Unknown Source)
后续无论发送多少次请求, 则都是抛出另一个异常
nested exception is : Could not initialize class
: Handler dispatch failed; nested exception is : Could not initialize class
at (:982)
at (:901)
at (:970)
at (:872)
at (:707)
at (:846)
at (:790)
at (:85)
at $(:129)
at (:55)
at (:107)
at (:61)
at $(:131)
at (:110)
at (:107)
at (:61)
at $(:131)
at com.(:116)
at (:61)
at $(:131)
at com.(:153)
at (:61)
at $(:131)
at (:197)
at (:107)
at (:61)
at $(:131)
at (:106)
at (:107)
at (:61)
at $(:131)
at (:84)
at (:62)
at (:36)
at (:131)
at (:57)
at (:43)
at (:46)
at (:64)
at (:60)
at (:77)
at (:43)
at (:43)
at (:43)
at (:292)
at $100(:81)
at $(:138)
at $(:135)
at $(:48)
at $(:43)
at (:272)
at $000(:81)
at $(:104)
at (:211)
at $(:809)
at (Unknown Source)
at $(Unknown Source)
at (Unknown Source)
这里面有 2 个问题
- 请求进来后, 为什么会导致异常
- 第一次异常为 FileNotFoundException, 第二次及后续相同的请求, 抛出的则是 NoClassDefFoundError, 2 种不同的异常, 第一次特殊了
3 第一个问题
通过异常日志定位到出现异常的地方为
public final class Conduits {
static {
NULL_FILE_CHANNEL = AccessController.doPrivileged(new PrivilegedAction<FileChannel>() {
public FileChannel run() {
final String osName = System.getProperty("", "unknown").toLowerCase(Locale.US);
try {
if (osName.contains("windows")) {
// 抛出异常的位置
return new FileOutputStream("NUL:").getChannel();
} else {
return new FileOutputStream("/dev/null").getChannel();
}
} catch (FileNotFoundException e) {
throw new IOError(e);
}
}
});
}
}
抛出异常的位置 new FileOutputStream(“NUL:”).getChannel()。 一行代码, 大体的逻辑为: 在 windows 系统中, 打开 NUL: 这个文件, 获取到其通道。
经过查询, 这里的 NUL:, 所在的文件路径为 C:\Windows\System32\drivers\。
文件的作用: It allows a user to trivially throw away program output, or to supply empty input.
和 Linux 的 /dev/null作用类似, 可以接收任意的输入, 不产生输出。Linux 中经常将一下无用的日志都指向这个位置!
初步怀疑是 这个文件有异常, 从网上下载了一个正常的, 进行替换后, 还是同样的问题, 也就是 文件正常。
排除是 文件的问题后。 猜测是否为 new FileOutput(“NUL:”), JDK 在实现上有问题。
最终在 * 的 "Java: FileOutputStream(“NUL:”) not working after Java upgrade " 这篇文章中找到了问题的解决方案。
同时顺藤摸瓜定位到官网中, 找到了问题所在, 的确是 JDK-8u331 这个版本的一个 bug。
bug 详情可以看这里 “JDK-8285445 : cannot open file “NUL:””
总结一下, JDK-8u331 中尝试避免去访问 ADS (alternate data streams), 而在代码实现上同时造成了 JDK 对设备文件的访问受阻。
解决方案:
- 在系统属性中添加 , 值为 false, 停用这个新特性即可
- JDK-8u333 会对这种情况进行修复, 也就是更换 JDK 版本
到此第一个问题解决了。
4 第二个问题
第二个问题, 为什么第二次及后面报的异常会和第一次的不同。
在上面的流程中, 2 次请求出现不同的异常:
第一次请求抛出的是 FileNotFoundException
第二次后的所有请求抛出的是 NoClassDefFoundError
NoClassDefFoundError 异常可以简单的理解为 Java 编译期能够找得到对应的 Class, 而到了运行时, 找不到合适的 Class。
抛出这个异常的线程会直接被停止, 即使 try-catch 住了异常, 线程也会被终止掉。
导致 NoClassDefFoundError 的原因有总的来说有 2 种
- 运行中找不到对应的类 (可能是 classpath 中没有这个 class, 存在多个类加载器等情况), 抛出的异常一般为 : 没有找到的类的路径
- 类加载中初始化失败, 即 class 的 <clinit>(),也就是类的静态属性和静态代码出现了异常, 抛出的异常一般为 NoClassDefFoundError: Could not initialize class 类的路径
前者是真的在运行中没有找到这个类, 后者是加载类失败。
结合上面出现异常的位置的代码,可以很快的知道第二次及后续出现的异常是因为第一次加载 Conduits 类执行其静态代码块失败了, 后续加载这个 Conduits 类, 直接不加载, 抛出 NoClasseDefFoundError 这个异常了
5 参考
: NUL: (系统找不到指定的文件。)
Java 运行时发生 NoClassDefFoundError: Could not initialize class 的解决方法
Java: FileOutputStream(“NUL:”) not working after Java upgrade
JDK-8285445 : cannot open file “NUL:”