【问题标题】:NoClassDefFoundError sometimes for a standard class after System.out.close()NoClassDefFoundError 有时对于 System.out.close() 之后的标准类
【发布时间】:2015-09-05 15:12:31
【问题描述】:

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

异常是从logback(1.1.3 版)引发的,似乎是在它遍历要写入的附加程序列表时(我猜它使用CopyOnWriteArrayList 来保存附加程序列表):

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 启动的守护进程的子进程运行时才会出现。

实验(通过使用static 代码块)表明主线程类加载器可以在程序启动时加载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 classloader noclassdeffounderror logback


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 2014-04-05
    • 1970-01-01
    • 2018-04-26
    • 1970-01-01
    • 2017-08-29
    • 2014-04-12
    • 2012-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多