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

Unexplainable ClassNotFoundException in server unmanaged thread

在以下情况下,我遇到了一个奇怪的问题ClassNotFoundException: org.apache.commons.io.IOUtils

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

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

在应用程序关闭时,我调用 tailer.stop() 来停止线程。然后 Tailer 执行的最后一件事是一个 finally 块,它在其中关闭日志 reader.

} finally {
    IOUtils.closeQuietly(reader);
}

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

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

1.确定 Tailer 的资源:

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

Output: 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 which is the right jar

2。它使用什么 classloader?: Tailer.class.getClassLoader();

com.ibm.ws.classloader.CompoundClassLoader@8d336acd[war:application/myApp.war] Again this is correct.

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

Again a ClassNotFoundException

4.现在,最令人惊讶的是:如果我在 运行 方法的开头添加如下所示的 Class.forName(),则 class 已正确加载,现在 IOUtils 在 finally 块中调用作品。怎么会??

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 中的 class。但是如果你在 运行() 的开头强制加载 class,那么它就不会再失败了。有什么想法吗?

这是整个堆栈跟踪:

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 和 #2 只是数据点,表明常见问题不是原因。它们不一定表示问题的根源。

项目 #3 表明了这个问题。根据该项目,似乎 类 已从内存中卸载。我查看了一些文档,这些文档会显示 ClassLoader 在什么情况下会抛出异常,但找不到任何文档。因此,我猜测 类 已被卸载并且应用程序的状态是 warfile 本身已被释放。进一步的猜测是无关紧要的。

第 4 项确实很好奇。 Class.forName 上的文档说 "Instances of the class Class represent classes and interfaces in a running Java application." 和 "Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader."。这似乎意味着即使 Class 已从内存中卸载 Class.forName 也应该能够加载它。

您示例中的第 4 项在 finally 块中没有 Class.forName。我认为这只是粘贴代码的人工制品,您确实将它放在那里并引发了错误。

更新:与 OP 讨论后,以下建议可以解决问题。 在调用 tailer.stop() 时,在调用后放置一个 thread.sleep() 几秒钟。在从 ClassLoader.

卸载应用程序之前,这应该为 finally 调用留出足够的时间完成 (closeQuietly(reader))