NoClassDefFoundError 有时对于 System.out.close() 之后的标准 class
NoClassDefFoundError sometimes for a standard class after System.out.close()
我的 java 程序由于 NoClassDefFoundError
而神秘崩溃。神秘之处在于异常消息表明有问题的 class 是 java/util/concurrent/CopyOnWriteArrayList$COWIterator
,它是 JRE 的一部分。在其他 JRE classes 没有问题地加载后抛出异常。
异常是从 logback
(版本 1.1.3)抛出的,似乎是在它遍历要写入的附加程序列表时(我猜它使用 CopyOnWriteArrayList
来保存列表appenders):
Thread [main] (Suspended (exception NoClassDefFoundError))
CopyOnWriteArrayList<E>.iterator() line: 959
AppenderAttachableImpl<E>.appendLoopOnAppenders(E) line: 47
Logger.appendLoopOnAppenders(ILoggingEvent) line: 273
Logger.callAppenders(ILoggingEvent) line: 260
Logger.buildLoggingEventAndAppend(String, Marker, Level, String, Object[], Throwable) line: 442
Logger.filterAndLog_0_Or3Plus(String, Marker, Level, String, Object[], Throwable) line: 396
Logger.info(String) line: 600
MyProgramTextLogger.logStarting() line: 303
MyProgram.run() line: 1686
MyProgram.runProgram(MyProgram) line: 790
MyProgram.main(String[]) line: 712
当然,logback
代码在创建要迭代的列表时必须已经加载了 CopyOnWriteArrayList
class,因此 classloader 必须那时已经正确地完成了它的工作。
更神秘的是,如果我 运行 程序在前台,或者作为由 systemd
控制的守护进程,则不会发生此问题。它仅在我 运行 我的程序作为由 systemd
启动的守护进程的子进程时出现。
实验(通过使用 static
代码块)表明主线程 classloader 可以在程序启动时加载 CopyOnWriteArrayList$COWIterator
class OK(程序然后为不同的 JRE class 抛出 NoClassDefFoundError
)。就好像 classloader 已经决定失去加载 classes 的能力。
我没有向 java
程序传递任何特殊参数。我为 java
选项使用了完整路径名并提供了 -showversion
选项以确保我每次都使用相同的 JVM 和 JRE。我的命令行很简单:
/usr/bin/java \
-showversion \
-jar /home/myuser/lib/my-app.jar --1 --latest
Java 报告其版本为
java version "1.7.0_75"
OpenJDK Runtime Environment (rhel-2.5.4.2.el7_0-x86_64 u75-b13)
OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)
我的程序没有进行任何显式 class 加载,也没有自定义 class 加载程序:它使用默认的 class 加载程序。然而,bkail has pointed out that it should not matter if I did, because the Java platform classes should be loaded by the boot class loader rather than the application class loader.
在我的程序失败的地方,我的代码没有创建任何线程。我的代码不包含任何本机代码,因此没有 "external" 可能混淆事物的线程。但是,我相信 logback
创建了一些线程,当然还有正常的 Java 守护进程线程。
还有一个神秘的方面。在创建 logback
记录器(使用 org.slf4j.LoggerFactory.getLogger(MyProgram.class)
)和调用导致异常的 Logger.info(String)
方法之间,基本上我的应用程序所做的就是关闭标准输入、标准输出和标准错误输出流,使用对这个方法的调用:
private static void closeSystemStreams() {
try {
System.in.close();
} catch (IOException e) {
// Do nothing; should never happen, and there is nothing we can do if
// it does
}
System.out.close();
System.err.close();
}
如果我省略关闭代码,程序 运行 就可以了。现在,我可以理解关闭这些流在某些情况下可能会失败(和 some recommend against it),但为什么它会干扰 bootstrap class 加载程序?
我需要做什么才能确保 logback
或 JVM 不会出现此类 class 加载问题?
如果关闭标准流,某些 Java 库代码似乎会变得混乱。也许某处有一个文件描述符值的硬编码检查。这可能是错误,也可能不是错误,具体取决于您对行为的解释程度。不关闭标准流似乎是最安全的。
我的 java 程序由于 NoClassDefFoundError
而神秘崩溃。神秘之处在于异常消息表明有问题的 class 是 java/util/concurrent/CopyOnWriteArrayList$COWIterator
,它是 JRE 的一部分。在其他 JRE classes 没有问题地加载后抛出异常。
异常是从 logback
(版本 1.1.3)抛出的,似乎是在它遍历要写入的附加程序列表时(我猜它使用 CopyOnWriteArrayList
来保存列表appenders):
Thread [main] (Suspended (exception NoClassDefFoundError))
CopyOnWriteArrayList<E>.iterator() line: 959
AppenderAttachableImpl<E>.appendLoopOnAppenders(E) line: 47
Logger.appendLoopOnAppenders(ILoggingEvent) line: 273
Logger.callAppenders(ILoggingEvent) line: 260
Logger.buildLoggingEventAndAppend(String, Marker, Level, String, Object[], Throwable) line: 442
Logger.filterAndLog_0_Or3Plus(String, Marker, Level, String, Object[], Throwable) line: 396
Logger.info(String) line: 600
MyProgramTextLogger.logStarting() line: 303
MyProgram.run() line: 1686
MyProgram.runProgram(MyProgram) line: 790
MyProgram.main(String[]) line: 712
当然,logback
代码在创建要迭代的列表时必须已经加载了 CopyOnWriteArrayList
class,因此 classloader 必须那时已经正确地完成了它的工作。
更神秘的是,如果我 运行 程序在前台,或者作为由 systemd
控制的守护进程,则不会发生此问题。它仅在我 运行 我的程序作为由 systemd
启动的守护进程的子进程时出现。
实验(通过使用 static
代码块)表明主线程 classloader 可以在程序启动时加载 CopyOnWriteArrayList$COWIterator
class OK(程序然后为不同的 JRE class 抛出 NoClassDefFoundError
)。就好像 classloader 已经决定失去加载 classes 的能力。
我没有向 java
程序传递任何特殊参数。我为 java
选项使用了完整路径名并提供了 -showversion
选项以确保我每次都使用相同的 JVM 和 JRE。我的命令行很简单:
/usr/bin/java \
-showversion \
-jar /home/myuser/lib/my-app.jar --1 --latest
Java 报告其版本为
java version "1.7.0_75"
OpenJDK Runtime Environment (rhel-2.5.4.2.el7_0-x86_64 u75-b13)
OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)
我的程序没有进行任何显式 class 加载,也没有自定义 class 加载程序:它使用默认的 class 加载程序。然而,bkail has pointed out that it should not matter if I did, because the Java platform classes should be loaded by the boot class loader rather than the application class loader.
在我的程序失败的地方,我的代码没有创建任何线程。我的代码不包含任何本机代码,因此没有 "external" 可能混淆事物的线程。但是,我相信 logback
创建了一些线程,当然还有正常的 Java 守护进程线程。
还有一个神秘的方面。在创建 logback
记录器(使用 org.slf4j.LoggerFactory.getLogger(MyProgram.class)
)和调用导致异常的 Logger.info(String)
方法之间,基本上我的应用程序所做的就是关闭标准输入、标准输出和标准错误输出流,使用对这个方法的调用:
private static void closeSystemStreams() {
try {
System.in.close();
} catch (IOException e) {
// Do nothing; should never happen, and there is nothing we can do if
// it does
}
System.out.close();
System.err.close();
}
如果我省略关闭代码,程序 运行 就可以了。现在,我可以理解关闭这些流在某些情况下可能会失败(和 some recommend against it),但为什么它会干扰 bootstrap class 加载程序?
我需要做什么才能确保 logback
或 JVM 不会出现此类 class 加载问题?
如果关闭标准流,某些 Java 库代码似乎会变得混乱。也许某处有一个文件描述符值的硬编码检查。这可能是错误,也可能不是错误,具体取决于您对行为的解释程度。不关闭标准流似乎是最安全的。