什么可能导致长时间运行的进程中突然出现 ClassNotFoundException?
Posted
技术标签:
【中文标题】什么可能导致长时间运行的进程中突然出现 ClassNotFoundException?【英文标题】:What could cause a sudden ClassNotFoundException in a long running process? 【发布时间】:2013-09-13 10:59:15 【问题描述】:我们有一个由 Jetty 运行的非常小的 Web 服务(少于 1K 行代码)。即使在我们的压力测试阶段,该服务也始终运行良好。但是,在运行 13 天后,我们在同一天在两个节点中遇到了 ClassNotFoundException。
奇怪的是,找不到的类已经存在(它是启动例程的一部分,并且经常用于服务先前的请求)。实际上,只需重新启动该过程即可解决问题。两个节点都在不同的机器中,并且彼此独立。它们不依赖于外部资源,除了一个 JMS 连接。
我在谷歌搜索时找不到相关信息,因为大多数报告的问题与启动 Java 进程时类路径中缺少类有关,这不是我们的情况。我们怀疑可能存在以某种方式破坏 JVM 内存的内存泄漏,但这无法解释为什么相同的问题会同时在两个节点中发生。在过去的五天里,我们一直在运行密集的压力测试,附加了一个 JVM 监视器和一个内存泄漏分析器,一切似乎都很好。对于此测试,我们将进程内存从 2GB 减少到 512MB。
详情:
使用 Java HotSpot(TM) 64 位服务器 VM(内部版本 16.3-b01,混合模式) 使用 jetty-runner-8.1.0.RC5.jar 原始命令行:java -Xmx2048M -jar jetty-runner-8.1.0.RC5.jar --port 5000 webapp.war Intel Xeon E5-2680 8 核 (x2) + 16GB RAM 红帽企业 Linux 6 正在使用的一些框架:JBoss Resteasy、Spring IoC、Guava。您能否提供一些想法,让 JVM 突然“忘记”以前加载的类的存在,无法再次加载它?
Caused by: java.lang.ClassNotFoundException: com.a.b.c.SomeClass
at java.net.URLClassLoader$1.run(URLClassLoader.java:202) ~[na:1.6.0_37]
at java.security.AccessController.doPrivileged(Native Method) ~[na:1.6.0_37]
at java.net.URLClassLoader.findClass(URLClassLoader.java:190) ~[na:1.6.0_37]
at java.lang.ClassLoader.loadClass(ClassLoader.java:306) ~[na:1.6.0_37]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) ~[na:1.6.0_37]
at java.lang.ClassLoader.loadClass(ClassLoader.java:247) ~[na:1.6.0_37]
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:424) ~[na:na]
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:377) ~[na:na]
at java.lang.Class.forName0(Native Method) ~[na:1.6.0_37]
at java.lang.Class.forName(Class.java:247) ~[na:1.6.0_37]
at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:95) ~[na:1.6.0_37]
at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:107) ~[na:1.6.0_37]
at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:31) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseSig(AnnotationParser.java:370) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseClassValue(AnnotationParser.java:351) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:280) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:222) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69) ~[na:1.6.0_37]
at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52) ~[na:1.6.0_37]
at java.lang.reflect.Field.declaredAnnotations(Field.java:1014) ~[na:1.6.0_37]
at java.lang.reflect.Field.getDeclaredAnnotations(Field.java:1007) ~[na:1.6.0_37]
编辑:
有人提到我在 Win 下使用 NFS 挂载时,可能会发生 JVM 决定卸载一个类,然后在需要时重新加载它。如果在此过程中 NFS 连接中断,则文件句柄将无效,并且重新加载将失败并出现类似的堆栈跟踪。在我们的例子中,我们使用的是 Linux,所有涉及的文件都在同一个挂载中,这是一个本地硬盘。只是为了进行更多测试,我已经 CD 到 Jetty 临时目录并手动删除了一个众所周知的特定服务类。如果 JVM 卸载它然后尝试从 classes 目录重新加载它,它将失败。虽然这并不能解释最初的问题,但它可能会将更多信息放在桌面上......
【问题讨论】:
你的堆栈跟踪中有更多行吗?可能是由于某些实例试图动态修改 webapp 容器的类路径。 @Farid:是的。上面的行与无法加载类的 Jackson 反序列化器有关(出于保密原因,我将其名称混淆为 com.a.b.c.SomeClass)。出于同样的原因,我不能复制粘贴它。但是,我将很快编辑该问题以添加更多信息。谢谢, 我从事的大多数项目也使用 jackson,但从未遇到过像您这样的问题。我能看到的唯一区别是我们通常使用tomcat而不是jetty,而我从不使用jackson的注释。您是否认为可以在 tomcat 实例下尝试从 jetty 丢弃任何类路径修改机制,或者不为 jackson 提供任何注释(因为您的应用程序很小)? 我能想到的一个是热部署扫描器,即码头检测到您的应用程序文件发生变化并重新加载整个类路径。如果您正在使用此功能,请禁用它 主要问题仍然是为什么再次加载该类?您是否按照@gerrytan 的建议禁用了热部署? 【参考方案1】:这是正在发生的事情:
-
当使用上面详述的 cmd 启动服务时,Jetty 在“/tmp”下创建一个子目录,其中包含 JVM 加载的应用程序类和资源。
在一段时间不活动后(在我们的特定场景中,为 13 到 20 天),该目录就会消失。结果,JVM 无法加载该文件。我们仍然不知道 JVM 是否在此错误之前卸载了该类,或者它为什么尝试重新读取 *.class 文件。查看源代码并了解这一点会很有趣,但这不在我们的短期待办事项列表中。
只需重新启动 Jetty 即可重新创建丢失的目录,并再次启动服务。
我们得到的一个很好的提示是,有些人在 Windows 上通过 NFS 加载 JAR 中的资源时报告了类似的问题(如果网络连接暂时丢失,NFS 句柄将变得无效,并且 JVM 失败并出现类似错误)。这不是我们的情况(/tmp 是本地存储),但非常相似。
感谢大家的帮助。
【讨论】:
感谢您,我们能够在我们的 Jetty 9 服务器上修复一个非常相似的错误。在我们的例子中,服务器在 CentOS 发行版上运行,如果在 10 天内没有访问过/tmp
目录中的文件,则每天都有一个 cron 作业清理它们。我们可以通过简单地在 Jetty home 下创建一个名为 work
的目录并重新启动服务器来解决这个问题,Jetty 不会使用 /tmp
来分解 WAR 文件。【参考方案2】:
堆栈跟踪告诉我们,它是关于处理注释的,与加载类以执行代码无关。看来注解处理器试图通过注解元素的ClassLoader
来解析注解成员的值。
换句话说,您有一个具有类类型值的注释,如@Foo(xyz=ABC.class)
和一个使用此构造注释的类或成员,但在运行时无法通过注释元素的ClassLoader
访问类ABC
。
这与该类已经通过另一个ClassLoader
加载这一事实并不冲突。
【讨论】:
听起来有点奇怪。你如何解释这个问题是在很长一段时间后才出现的,如你所写的大约 13 天? 我对 EE-framework/webservice 的ClassLoader
结构和活动了解不多。是否涉及重新加载/重新部署?
我认为有任何重新加载/重新部署的主要情况是当您启用热部署并且某些资源发生变化时,但我认为这里不是这种情况并且您关闭了热按照 cmets 中的@gerrytan 建议进行部署。我假设您将此码头配置仅用于开发而不是用于以后的测试而不是用于生产?以上是关于什么可能导致长时间运行的进程中突然出现 ClassNotFoundException?的主要内容,如果未能解决你的问题,请参考以下文章