java – NoClassDefFoundError有时用于System.out.close()之后的标准类

由于NoClassDefFoundError,我的java程序神秘崩溃.问题是异常消息表明有问题的类是java / util / concurrent / CopyOnWriteArrayList $COWIterator,它是JRE的一部分.其他JRE类加载后没有问题抛出异常.

从logback(版本1.1.3)抛出异常,似乎是在迭代通过要写入的appender列表时(我猜它使用CopyOnWriteArrayList来保存appender列表):

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类,因此类加载器必须在此时正确完成其工作.

如果我在前台运行程序,或者作为systemd控制的守护程序进程,则不会发生这个问题.它仅在我将程序作为systemd启动的守护进程的子进程运行时才会显示.

实验(通过使用静态代码块)显示主线程类加载器可以在程序启动时加载CopyOnWriteArrayList $COWIterator类OK(程序然后为不同的JRE类抛出NoClassDefFoundError).就好像类加载器决定失去加载类的能力一样.

我没有向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)

我的程序没有进行任何显式类加载,也没有自定义类加载器:它使用默认的类加载器.但是,bkail已经指出,如果我这样做应该没关系,因为Java平台类应该是loaded by the boot class loader rather than the application class loader.

在我的程序失败的时候,我的代码没有创建任何线程.我的代码不包含任何本机代码,因此没有可能混淆事物的“外部”线程.我相信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)关闭这些流可能会失败,但为什么它会干扰引导类加载器?

我需要做些什么来确保logback或JVM不会出现这些类加载问题?

解决方法:

如果标准流关闭,似乎某些Java库代码会混淆.也许在某处对文件描述符的值进行了硬编码检查.这可能是也可能不是一个错误,取决于你如何慷慨解释这种行为.关闭标准流似乎是最安全的.

上一篇:BUG记录-java.lang.NoClassDefFoundError


下一篇:寻找代码转换器将普通Java代码转换为反射式代码