1 环境

os : window 10
jdk : JDK-8u331
SpringBoot: 2.0.0.RELEASE

2 现象

一个 SpringBoot 的项目, 使用了 undertown 作为 web 容器的项目。
项目启动成功后, 第一次发起 http 请求接口, 任意一个接口都可以, 会直接抛出如下异常:
nested exception is java.io.IOError: java.io.FileNotFoundException: Invalid file path

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.io.IOError: java.io.FileNotFoundException: Invalid file path at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:982) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at com.lcn29.framework.web.filter.CustomTraceFilter.doFilter(CustomTraceFilter.java:116) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at com.lcn29.framework.tracer.sofa.springmvc.SpringMvcSofaTracerFilter.doFilter(SpringMvcSofaTracerFilter.java:153) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:211) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:809) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)

后续无论发送多少次请求, 则都是抛出另一个异常
nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.xnio.channels.Channels

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.xnio.channels.Channels at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:982) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at com.lcn29.framework.web.filter.CustomTraceFilter.doFilter(CustomTraceFilter.java:116) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at com.lcn29.framework.tracer.sofa.springmvc.SpringMvcSofaTracerFilter.doFilter(SpringMvcSofaTracerFilter.java:153) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:211) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:809) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)

这里面有 2 个问题

  1. 请求进来后, 为什么会导致异常
  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("os.name", "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\null.sys。

null.sys 文件的作用: It allows a user to trivially throw away program output, or to supply empty input. 和 Linux 的 /dev/null作用类似, 可以接收任意的输入, 不产生输出。Linux 中经常将一下无用的日志都指向这个位置!

初步怀疑是 null.sys 这个文件有异常, 从网上下载了一个正常的, 进行替换后, 还是同样的问题, 也就是 null.sys 文件正常。
排除是 null.sys 文件的问题后。 猜测是否为 new FileOutput(“NUL:”), JDK 在实现上有问题。

最终在 StackOverflow 的 “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 对设备文件的访问受阻。

解决方案:

  1. 在系统属性中添加 jdk.io.File.enableADS, 值为 false, 停用这个新特性即可
  2. JDK-8u333 会对这种情况进行修复, 也就是更换 JDK 版本

到此第一个问题解决了。

4 第二个问题

第二个问题, 为什么第二次及后面报的异常会和第一次的不同。

在上面的流程中, 2 次请求出现不同的异常:

第一次请求抛出的是 FileNotFoundException
第二次后的所有请求抛出的是 NoClassDefFoundError

NoClassDefFoundError 异常可以简单的理解为 Java 编译期能够找得到对应的 Class, 而到了运行时, 找不到合适的 Class。
抛出这个异常的线程会直接被停止, 即使 try-catch 住了异常, 线程也会被终止掉。

导致 NoClassDefFoundError 的原因有总的来说有 2 种

  1. 运行中找不到对应的类 (可能是 classpath 中没有这个 class, 存在多个类加载器等情况), 抛出的异常一般为 java.lang.NoClassDefFoundError: 没有找到的类的路径
  2. 类加载中初始化失败, 即 class 的 <clinit>(),也就是类的静态属性和静态代码出现了异常, 抛出的异常一般为 NoClassDefFoundError: Could not initialize class 类的路径

前者是真的在运行中没有找到这个类, 后者是加载类失败。

结合上面出现异常的位置的代码,可以很快的知道第二次及后续出现的异常是因为第一次加载 Conduits 类执行其静态代码块失败了, 后续加载这个 Conduits 类, 直接不加载, 抛出 NoClasseDefFoundError 这个异常了

5 参考

java.io.FileNotFoundException: NUL: (系统找不到指定的文件。)
Java 运行时发生 NoClassDefFoundError: Could not initialize class 的解决方法
Java: FileOutputStream(“NUL:”) not working after Java upgrade
JDK-8285445 : cannot open file “NUL:”