相信很多Java开发人员都对这个常见却不招人待见的java.lang.ClassNotFoundException并不陌生。出现这个异常的原因大家都清楚(classpath路径下缺少class文件或者jar包了,或者是类加载器委派的问题等),不过对于它给JVM带来的性能影响可能就不了解了。这个异常可能会严重影响应用程序的响应时间和可伸缩性。
大型的J2EE企业级应用,可能会同时部署有多个应用,由于同时运行着多个类加载器,就很容易导致这类问题。除非发现有业务确实受到影响或者日志监控比较详细,否则很有可能出现了ClassNotFoundException你也不知道,这导致的结果是:JVM类加载的IO开销和线程间的锁竞争给系统带来的性能问题。
本文及文中的示例程序将会告诉你,应当谨慎对待生产系统中出现的ClassNotFoundException异常,并正确的解决它。
Java类加载:性能优化的死角
想要正确理解这个性能问题,首先你得明白Java类加载模型的机制。ClassNotFoundException意味着JVM无法找到或者加载某个Java类:
- +Class.forName()方法
- +ClassLoader.findSysteClass()方法
- +ClassLoader.loadClass()方法
-
尽管在JVM的生命周期内,你的应用程序里面的Java类应该只会加载一次,但有些应用可能会依赖于动态的类加载机制。
不管怎么说,不停地加载失败总是很影响性能的,尤其是JDK中默认的java.lang.ClassLoader进行加载的时候。事实上,1.7以上版本的JDK,为了向下兼容,同一个类不能同时被加载,除非这个类加载器被标记为“支持并发”的。你要时刻牢记,同步操作是在类这一级实现的,由于多个Java线程并发地加载,同一个类不停的加载失败会引发线程间的锁竞争。如果是JDK1.6以前则情况更糟糕,因为同步操作是在类加载器这一层完成的。
由于这个原因,像JBoss WildFly8等JavaEE容器都使用了它们内部的并发类加载器来完成应用程序的类加载。这些类加载器都在更细粒度上进行加锁,这样同一个类加载器的实例可以同时加载多个不同的类。这和JDK1.7的优化不谋而和,它支持的并发的自定义类加载器也是防止类加载器出现死锁的现象。
不过,像java.*的系统级别的类,它们的加载还是会由JDK默认的类加载器来完成。这意味着连续的类加载失败还是可能会引发严重的线程锁竞争。这也正是本文接下来要重现并演示的场景。
线程锁竞争——问题复现
为了重现并模拟这个问题,我们按照以下的规范写了一个简单的程序:
+一个JAX-RS WEB服务执行Class.forName()来加载一个系统包下不存在的类:
String className =”java.lang.WrongClassName”;
Class.forName(className);
- JRE: HotSpot JDK 1.7 64位
- Java EE容器:JBoss WildFly8
- 压测工具:Apache JMeter
- Java监控工具:JVisualVM
- Java故障分析:JVM Thread Dump
JAX-RS服务同时有20个线程在并发的执行。每一次调用都会触发一个ClassNotFoundException。为了减少IO的影响,日志也完全关闭了,我们只关注类加载的竞争冲突。
现在我们来看下JVisualVM在半分钟到一分钟内的监测结果。可以很明显的看到,很多线程都阻塞住了,在等待获取一个对象锁。
分析JVM的threaddump很容易就能定位出问题:线程锁竞争。从运行栈的跟踪信息可以看到,JBoss把类加载的任务委派给了JDK的类加载器。为什么?这是因为这个不存在的java类被认为是属于系统类路径底下,因此JBoss会把它的加载委派给系统的类加载器。这样因为这个类触发了系统级的同步,其它线程只能等待获取锁才能加载它。
很多线程都在等待获取0x00000000ab84c0c8的锁:
"default task-15" prio=6 tid=0x0000000014849800 nid=0x2050 waiting for monitor entry [0x000000001009d000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:403)
- waiting to lock <0x00000000ab84c0c8> (a java.lang.Object)
// Waiting to acquire a LOCK held by Thread “default task-20”
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356) // JBoss now delegates to system ClassLoader..
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)
at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
at sun.reflect.GeneratedMethodAccessor15.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
这个线程是罪魁祸首——default task 20
"default task-20" prio=6 tid=0x000000000e3a3000 nid=0x21d8 runnable [0x0000000010e7d000]
java.lang.Thread.State: RUNNABLE
at java.lang.Throwable.fillInStackTrace(Native Method)
at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
- locked <0x00000000a09585c8> (a java.lang.ClassNotFoundException)
at java.lang.Throwable.<init>(Throwable.java:287)
at java.lang.Exception.<init>(Exception.java:84)
at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75)
at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82) // ClassNotFoundException! at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
- locked <0x00000000ab84c0e0> (a java.lang.Object)
at java.lang.ClassLoader.loadClass(ClassLoader.java:410)
- locked <0x00000000ab84c0c8> (a java.lang.Object) // java.lang.ClassLoader: LOCK acquired at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)
at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
现在我们把要加载类的名字更换到应用程序的包下面并重新运行这段程序。
String className = "org.ph.WrongClassName";
Class.forName(className);
可以看到,不再有阻塞的线程出现了。为什么?我们来看下JVM的thread dump来更好的理解下为什么会出现这个变化。
"default task-51" prio=6 tid=0x000000000dd33000 nid=0x200c runnable [0x000000001d76d000]
java.lang.Thread.State: RUNNABLE
at java.io.WinNTFileSystem.getBooleanAttributes(Native Method) // IO overhead due to JAR file search operation
at java.io.File.exists(File.java:772)
at org.jboss.vfs.spi.RootFileSystem.exists(RootFileSystem.java:99)
at org.jboss.vfs.VirtualFile.exists(VirtualFile.java:192)
at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:127)
at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:124)
at java.security.AccessController.doPrivileged(Native Method)
at org.jboss.as.server.deployment.module.VFSResourceLoader.getClassSpec(VFSResourceLoader.java:124)
at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:252)
at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:76)
at org.jboss.modules.Module.loadModuleClass(Module.java:526)
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:189) // JBoss now fully responsible to load the class
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:444) // Unchecked since using JDK 1.7 e.g. tagged as “safe” JDK
at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked(ConcurrentClassLoader.java:432)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:374)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at org.jboss.tools.examples.rest.MemberResourceRESTService.AppCLFailure(MemberResourceRESTService.java:196)
at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.AppCLFailure(Unknown Source)
at sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source)
上述的栈运行信息可以看出:
- 由于类名不再认为属于Java系统包下,不会发生类加载委派,也就没有同步操作。
- 由于JBoss认为JDK1.7是一个”安全“的JDK,它会使用ConcurrentClassLoader.performLoadClassUnchecked()方法,也就不会触发对象监视器锁。
- 没有同步也就不会产生线程锁竞争,也就不会出现没完没了的ClassNotFoundException异常。
值得注意的是JBoss在防止线程锁竞争方面做出了很大的改进,不过重复的类加载的尝试仍然会在一定程度上影响应用的性能,因为搜索JAR包查找Java类会有一定的IO开销,因此也仍需要采取正确的手段进行处理。
总结
希望你能喜欢这篇文章并且对类加载可能会带来的性能影响也有了更好的了解。JDK1.7和现代的Java EE容器都在类加载方面做了很多的优化,比如死锁和锁竞争方面,但是还是会存在潜在的问题。因此,我强烈建议你能密切的关注你的应用程序的表现,记录日志并确保类加载相关的异常比如java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError都能够正确的处理。
原创文章转载请注明出处:
http://it.deepinmind.com
英文原文链接
分享到:
相关推荐
`ClassNotFoundException` 是 Java 开发过程中常见的运行时异常,通常发生在尝试加载某个类时,系统无法在指定的类路径(ClassPath)中找到对应的 `.class` 文件。本文将深入探讨 `ClassNotFoundException` 的原因、...
然而,在开发过程中,开发者经常遇到各种问题,其中一种常见的问题是`ClassNotFoundException`。这个异常通常表明Java虚拟机(JVM)在尝试加载一个类时无法找到对应的.class文件。 在标题中提到的"Eclipse plugin...
Java中的`ClassNotFoundException`异常通常表示在尝试加载特定类时,JVM无法找到对应的字节码文件。这个异常可能由多种原因引起,但在本场景中,它与Java 9引入的模块系统及其对Java EE API的影响密切相关。 在Java...
在Java编程语言中,`ClassNotFoundException`是一个非常常见的运行时异常,它属于`java.lang.ClassNotFoundException`,当Java虚拟机(JVM)试图动态加载一个类并无法找到对应的.class文件时,就会抛出这个异常。...
在Java服务器页面(JSP)开发中,可能会遇到`ClassNotFoundException`异常,这通常是由于系统无法找到或加载指定的类导致的。此异常通常与类路径配置、库引用或JDBC驱动有关。以下是一些解决此类问题的有效方法: ...
在Java编程中,`java.lang.ClassNotFoundException`是一个常见的运行时异常,它表示JVM尝试加载一个类时,找不到对应的.class文件。在这个特定的错误中,我们看到的是`org.apache.commons.dbcp.BasicDataSource`,这...
"java.lang.ClassNotFoundException: com.microsoft.jdbc.sqlserver.SQLServerDriver" 解决方案 [Microsoft][SQLServer 2000 Driver for JDBC]Error establishing socket. 解决了jsp连接 sql server 2000的问题
然而,对于初学者来说,遇到错误是常有的事,比如“java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource”。这个异常表明在运行时,系统无法找到指定的类,即Apache Commons DBCP的...
在Java编程中,`ClassNotFoundException` 是一个常见的运行时异常,通常发生在尝试加载特定类时,JVM无法找到对应类的定义。在这个特定的情景中,错误提示 "java.lang.ClassNotFoundException: ...
在Java编程中,`java.lang.ClassNotFoundException` 是一个常见的运行时异常,通常发生在尝试通过类加载器加载指定类时,但找不到对应的字节码文件。在这个特定的问题中,`ClassNotFoundException` 引发的原因是缺少...
然而,"ClassNotFoundException"是一个常见的Java运行时异常,通常表示系统无法找到你尝试加载的类。在这个场景下,这个问题可能源于多个原因,主要涉及到项目的构建配置、依赖管理以及运行环境的差异。 首先,我们...
在Java编程中,`ClassNotFoundException` 是一个常见的运行时异常,通常发生在尝试加载类时,Java虚拟机(JVM)无法找到对应的字节码文件。在本例中,问题聚焦于 "org.jdom.input.SAXBuilder" 类,这涉及到Java的...
"Android Caused by: java.lang.ClassNotFoundException解决办法" 在 Android 开发中,ClassNotFoundException 是一个常见的异常,它通常发生在应用程序启动或运行时。这个异常的出现告诉我们,Java 虚拟机无法找到...
使用IDEA启动项目遇见ClassNotFoundException的解决方案 在使用IDEA启动项目时,可能会遇见ClassNotFoundException的错误,导致项目无法启动。本文将详细介绍使用IDEA启动项目遇见ClassNotFoundException的正确解决...
在Java编程中,`ClassNotFoundException` 是一个常见的运行时异常,表示尝试加载某个类时,在类路径中找不到对应的`.class`文件。在这个特定的场景中,异常堆栈跟踪显示 `Caused by: java.lang....
在Java编程中,`ClassNotFoundException` 是一个常见的运行时异常,通常表示尝试加载某个类时,在类路径中找不到该类的定义。在这个特定的场景中,异常堆栈跟踪显示了 `Caused by: java.lang.ClassNotFoundException...
解决AopConfigException类不存在。...Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.springframework.aop.framework.AopConfigException 解决方法是把这个文件放到WEB-INF文件夹下面
【SpringBoot】Error: java.lang.ClassNotFoundException: org.wltea.analyzer.core.IKSegmenter报错明细问题分析后记 报错明细 IDEA SpringBoot集成hadoop运行环境,本地启动项目,GET请求接口触发远程提交...