在各种 Log4j 上改变 ClassNotFoundException 类
Varying ClassNotFoundException on various Log4j classes
我们在基于大型 Swing/Spring 的应用程序中看到以下异常。该错误发生在触发 Spring 通过 commons-logging warn(String, Throwable)
报告警告的用户交互期间,后者又调用 log4j。不幸的是,我无法将这个问题分解成一个独立的示例。
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/log4j/spi/ThrowableInformation
at org.apache.log4j.spi.LoggingEvent.<init>(LoggingEvent.java:165)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.log(Category.java:856)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:487)
<company specific code>
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:749)
at java.awt.EventQueue.access0(EventQueue.java:97)
at java.awt.EventQueue.run(EventQueue.java:702)
at java.awt.EventQueue.run(EventQueue.java:696)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(ProtectionDomain.java:75)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:719)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.spi.ThrowableInformation
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 27 more
环境
- Java Windows XP SP3 上的 8u40,log4j-1.2.17,commons-logging-1.1.1,spring-4.1.2
- 没有class加载正在使用的基础设施,如 OSGI、Web 容器等
- 没有 Java 正在使用代理、分析器、测试工具
- 没有异常的 -XX 或 -D 参数
观察/我尝试过的事情
- 通过
System.getProperty("java.class.path")
手动打印出 class 路径以验证是否存在 log4j JAR - 是的,并且只有一个版本 1.2.17
- 无法加载的class在不同的运行之间有所不同,有时是ThowableInformation,有时是ThrowableRenderer等,但总是失败。
- 如果在应用程序
static void main()
中放置以下内容,则 log4j classes 会正确加载,并且会在控制台中看到警告,并且稍后不会发生异常。 <== 这证实了 log4j JAR 从应用程序中是可见的,假设没有多个 classloader 在玩...
代码
Log logger = LogFactory.getLog(getClass());
logger.warn("foo", new Exception());
- 已成功引用 class稍后无法从应用程序入口点加载的元素,例如ThowableInformation
代码
380 if (result == null) {
381 throw new ClassNotFoundException(name); // <=== here
382 }
383 return result;
- 在 IOException 中放置断点以防 "too many open files" 错误 - 未触发。
- 使用 -verbose 和 -Dsun.misc.URLClassPath.debug=true JVM 选项来调试 classloading...这表明各种 classes 是从 log4j jar 加载的...
例如
[Loaded org.apache.log4j.Category from file:/<path redacted>/log5j-1.2.17.jar]
[Loaded org.apache.log4j.Logger from file:/<path redacted>/log5j-1.2.17.jar]
[Loaded org.apache.log4j.Priority from file:/<path redacted>/log5j-1.2.17.jar]
- 在 ClassNotFoundException 中放置断点以查找更多信息
- 我可以看到 Launcher$AppClassLoader 有一个 URLClassLoader ucp
- ucp 包含 URLClassPath$FileLoader 和 URLClassPath$JarLoader 类型的加载器
- 我找到了引用 log4j-1.2.17 的 $JarLoader 实例。
- 即使 sun.misc.Launcher$AppClassLoader 的同一个实例之前从 JAR 中加载了 log4j classes,它似乎不再能够...
- 我能看到早期 class 加载工作和后来 class 加载失败之间的唯一区别是 URLClassPath$JarLoader 的 closed 属性在失败时设置为 true。 .
我的理论
- 某些资源已达到限制,classes 无法再加载,但出于某种原因没有报告。
- Spring、核心 Java 或 commons-logging 正在使用无法看到 log4j JAR 的私有 class-加载基础设施。
我的问题
- 还有其他人反对吗?
- 间歇性出现 ClassNotFoundException 的可能原因?我怎样才能困住他们?
- 更多调试建议 - 我没主意了。
根本原因
URLClassLoader 已关闭,这造成了 Java 报告 ClassNotFoundException 的不幸影响。
背景
为了支持使用 OSGI/Spring-DM 的应用程序的早期版本,我们在 spring XML 中有 bean,它允许从正确的包中加载资源,这太可怕了hack,但在给定代码、资源和 XML 文件时唯一可以完成的方法是在不同的包中...
<bean id="classLoader" class="org.example.internal.PluginModuleClassLoader" factory-method="getClassLoader" />
<bean id="imageLoader" class="org.example.ImageLoader">
<property name="classLoader" ref="classLoader" />
</bean>
public class PluginModuleClassLoader {
public static ClassLoader getClassLoader() {
return PluginModuleClassLoader.class.getClassLoader();
}
}
我们在 Spring 6 个月前从(2.5.5 升级到 4.1.2)并删除了 OSGI,因为它没有任何好处,但是由于删除它的成本,现在遗留的 PluginModuleClassLoader 机制被保留了下来来自 60 多个捆绑包。
它是如何关闭的
有问题的用户交互关闭一个应用程序上下文并加载另一个...这具有破坏应用程序上下文的副作用。 Spring 自动销毁所有bean。在 classLoader bean 的情况下,它自动调用找到 close() 方法,即 URLClassLoader.close() 这意味着所有后续查找都失败并显示 ClassNotFoundException
我们在基于大型 Swing/Spring 的应用程序中看到以下异常。该错误发生在触发 Spring 通过 commons-logging warn(String, Throwable)
报告警告的用户交互期间,后者又调用 log4j。不幸的是,我无法将这个问题分解成一个独立的示例。
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/log4j/spi/ThrowableInformation
at org.apache.log4j.spi.LoggingEvent.<init>(LoggingEvent.java:165)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.log(Category.java:856)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:487)
<company specific code>
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:749)
at java.awt.EventQueue.access0(EventQueue.java:97)
at java.awt.EventQueue.run(EventQueue.java:702)
at java.awt.EventQueue.run(EventQueue.java:696)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(ProtectionDomain.java:75)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:719)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.spi.ThrowableInformation
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 27 more
环境
- Java Windows XP SP3 上的 8u40,log4j-1.2.17,commons-logging-1.1.1,spring-4.1.2
- 没有class加载正在使用的基础设施,如 OSGI、Web 容器等
- 没有 Java 正在使用代理、分析器、测试工具
- 没有异常的 -XX 或 -D 参数
观察/我尝试过的事情
- 通过
System.getProperty("java.class.path")
手动打印出 class 路径以验证是否存在 log4j JAR - 是的,并且只有一个版本 1.2.17 - 无法加载的class在不同的运行之间有所不同,有时是ThowableInformation,有时是ThrowableRenderer等,但总是失败。
- 如果在应用程序
static void main()
中放置以下内容,则 log4j classes 会正确加载,并且会在控制台中看到警告,并且稍后不会发生异常。 <== 这证实了 log4j JAR 从应用程序中是可见的,假设没有多个 classloader 在玩...
代码
Log logger = LogFactory.getLog(getClass());
logger.warn("foo", new Exception());
- 已成功引用 class稍后无法从应用程序入口点加载的元素,例如ThowableInformation
代码
380 if (result == null) {
381 throw new ClassNotFoundException(name); // <=== here
382 }
383 return result;
- 在 IOException 中放置断点以防 "too many open files" 错误 - 未触发。
- 使用 -verbose 和 -Dsun.misc.URLClassPath.debug=true JVM 选项来调试 classloading...这表明各种 classes 是从 log4j jar 加载的...
例如
[Loaded org.apache.log4j.Category from file:/<path redacted>/log5j-1.2.17.jar]
[Loaded org.apache.log4j.Logger from file:/<path redacted>/log5j-1.2.17.jar]
[Loaded org.apache.log4j.Priority from file:/<path redacted>/log5j-1.2.17.jar]
- 在 ClassNotFoundException 中放置断点以查找更多信息
- 我可以看到 Launcher$AppClassLoader 有一个 URLClassLoader ucp
- ucp 包含 URLClassPath$FileLoader 和 URLClassPath$JarLoader 类型的加载器
- 我找到了引用 log4j-1.2.17 的 $JarLoader 实例。
- 即使 sun.misc.Launcher$AppClassLoader 的同一个实例之前从 JAR 中加载了 log4j classes,它似乎不再能够...
- 我能看到早期 class 加载工作和后来 class 加载失败之间的唯一区别是 URLClassPath$JarLoader 的 closed 属性在失败时设置为 true。 .
我的理论
- 某些资源已达到限制,classes 无法再加载,但出于某种原因没有报告。
- Spring、核心 Java 或 commons-logging 正在使用无法看到 log4j JAR 的私有 class-加载基础设施。
我的问题
- 还有其他人反对吗?
- 间歇性出现 ClassNotFoundException 的可能原因?我怎样才能困住他们?
- 更多调试建议 - 我没主意了。
根本原因
URLClassLoader 已关闭,这造成了 Java 报告 ClassNotFoundException 的不幸影响。
背景
为了支持使用 OSGI/Spring-DM 的应用程序的早期版本,我们在 spring XML 中有 bean,它允许从正确的包中加载资源,这太可怕了hack,但在给定代码、资源和 XML 文件时唯一可以完成的方法是在不同的包中...
<bean id="classLoader" class="org.example.internal.PluginModuleClassLoader" factory-method="getClassLoader" />
<bean id="imageLoader" class="org.example.ImageLoader">
<property name="classLoader" ref="classLoader" />
</bean>
public class PluginModuleClassLoader {
public static ClassLoader getClassLoader() {
return PluginModuleClassLoader.class.getClassLoader();
}
}
我们在 Spring 6 个月前从(2.5.5 升级到 4.1.2)并删除了 OSGI,因为它没有任何好处,但是由于删除它的成本,现在遗留的 PluginModuleClassLoader 机制被保留了下来来自 60 多个捆绑包。
它是如何关闭的
有问题的用户交互关闭一个应用程序上下文并加载另一个...这具有破坏应用程序上下文的副作用。 Spring 自动销毁所有bean。在 classLoader bean 的情况下,它自动调用找到 close() 方法,即 URLClassLoader.close() 这意味着所有后续查找都失败并显示 ClassNotFoundException