服务器非托管线程中无法解释的ClassNotFoundException

时间:2015-03-04 13:39:20

标签: java classloader classnotfoundexception websphere-8 apache-commons-io

在以下情况下我面临一个奇怪的ClassNotFoundException: org.apache.commons.io.IOUtils

我有一个在Websphere Application Server 8.0中运行的Web应用程序,它使用commons-io 2.0.1(此jar正确放置在类路径中)。

我正在使用org.apache.commons.io.input.Tailer来拖尾日志文件并在屏幕上显示其内容。 Tailer实现了Runnable,因此它在自己的Thread中运行。

在应用程序关闭时,我调用tailer.stop()来停止Thread。然后Tailer执行的最后一件事就是一个finally块,它关闭了日志阅读器。

} finally {
    IOUtils.closeQuietly(reader);
}

当它执行该行时,会有一个ClassNotFoundException: org.apache.commons.io.IOUtils

我做了一些测试,将一些代码添加到Tailer类中(并在jar中替换它)以确定它是否是ClassLoader问题:

1。确定Tailer的资源:

Tailer.class.getClassLoader().getResource("org/apache/commons/io/input/Tailer.class");

  

输出wsjar:file:/home/myuser/.m2/repository/commons-io/commons-io/2.0.1/commons-io-2.0.1.jar!/org/apache/commons/io/input/Tailer.class   这是正确的jar

2。它使用什么类加载器?: Tailer.class.getClassLoader();

  

com.ibm.ws.classloader.CompoundClassLoader@8d336acd[war:application/myApp.war]这也是正确的。

第3。尝试直接从以前的ClassLoader加载IOUtils: Tailer.class.getClassLoader().loadClass("org.apache.commons.io.IOUtils");

  

再次是ClassNotFoundException

4。现在,最令人惊讶的事情是:如果我在run方法的开头添加类似下面的Class.forName(),则正确加载类,现在finally块中的IOUtils调用正常工作。怎么来的?

public void run() {
     RandomAccessFile reader = null;
     try {
         Class.forName("org.apache.commons.io.IOUtils");
     }catch(Exception e){
         e.printStackTrace();
     }         
     try {
         ...
         while (run) {
            ...
         }
     } catch (Exception e) {
         listener.handle(e);
     } finally {
         IOUtils.closeQuietly(reader);
     }
 }

 /**
 * Allows the tailer to complete its current loop and return.
 */
 public void stop() {
     this.run = false;
 }

没有任何意义!它使用来自commons-io-2.0.1.jar的Tailer,但是它不能使用它在同一个jar中的类。但是如果你在run()的开头强制加载类,那么它就不会再失败了。有什么想法吗?

这是整个堆栈跟踪:

Exception in thread "Thread-88" java.lang.NoClassDefFoundError: org.apache.commons.io.IOUtils
    at org.apache.commons.io.input.Tailer.run(Tailer.java:319)
    at java.lang.Thread.run(Thread.java:784)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.IOUtils
    at java.net.URLClassLoader.findClass(URLClassLoader.java:434)
    at com.ibm.ws.bootstrap.ExtClassLoader.findClass(ExtClassLoader.java:230)
    at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:703)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:682)
    at com.ibm.ws.bootstrap.ExtClassLoader.loadClass(ExtClassLoader.java:123)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:62)
    at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:58)
    at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:566)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:566)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:665)
    ... 2 more

1 个答案:

答案 0 :(得分:0)

项目#1和#2只是表明常见问题不是原因的数据点。它们不一定表明问题的根源。

项目#3表示问题。看起来基于该项目,类已经从内存中卸载了。我查看了一些文档,这些文档将显示ClassLoader会在什么条件下抛出异常,但无法找到任何异常。所以,我猜测类已被卸载,应用程序的状态是warfile本身已被释放。进一步的猜测是无关紧要的。

第4项确实很好奇。关于Class.forName的文档说“类的实例表示正在运行的Java应用程序中的类和接口。”并且“类没有公共构造函数。相反,类对象是在加载类时由Java虚拟机自动构造的,并且通过调用类加载器中的defineClass方法。”这似乎意味着即使Class已从内存中卸载,Class.forName应该能够加载它。

在您的示例中,项目#4在finally块中没有Class.forName。我认为这只是粘贴代码的一个工件,你实际上把它放在那里并且它引发了一个错误。

更新:在与OP讨论后,以下建议完成了这项工作。 在调用tailer.stop()时,在调用后放置一个thread.sleep()几秒钟。这应该允许有足够的时间让最终调用完成(closeQuietly(reader)),然后从ClassLoader中卸载Application。