ClassNotFoundException: 真的会使你的JVM慢下来吗?

本文详细解析了Java类加载模型,特别是ClassNotFoundException异常的根源及其对JVM性能的影响。通过重现模拟问题,展示了类加载过程中的线程锁竞争如何导致应用程序响应时间变慢和可扩展性下降。文章强调了正确理解类加载机制的重要性,并提供了解决策略以减少这类问题的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文翻译自javacodegeeks   作者Pierre Hugues Charbonneau   译者:TonySpark 校对:郑旭东
大多数Java开发者比较熟悉这个普通的 java.lang.ClassNotFoundException。这个问题的根源逐渐被开发人员所了解(在ClassPath中找不到相关类或者类库,类加载器委托问题等等),然而它对于整个JVM及性能的影响却鲜为人知。这个异常会应用程序的响应时间和可扩展性有很大的影响。

 在部署了多个应用程序的大型Java EE企业系统中,由于运行期间有很多不同的类加载器,所以这种类型的问题出现的最多的。也就增加了面对未检测的ClassNotFoundException的风险,除非定义了明确的业务影响和实现了很好的日志监控,否则JVM类加载IO操作和线程锁竞争将会持续的影响应用程序的性能。

下文中的程序将演示你的客户生产系统中任何ClassNotFoundException都应认真对待并及时解决。

Java类Loading: 优化性能缺失的环节

只有正确的理解JAVA类加载模型才能正确的理解性能问题。ClassNotFoundException 本质上意味着JVM定位或通过下面的方法加载类是失败的:

1)Class.forName()方法

2)ClassLoader.findSystemClass() 方法

3)ClassLoader.loadClass()方法。

在JVM的生命周期中,应用程序中的类只会发生一次(当然也有动态重新部署功能),同时一些应用程序也依赖动态类加载操作。

然而,重复的成功或者失败的类加载操作是相当的惹人烦,尤其是试图使用JDK中 java.lang.ClassLoader 来进行加载操作。实际上,由于向后兼容性,在JDK1.7+ 除非类加载器被明确标记为具有并行能力(”parallel capable”)否则默认只会一次加载一个类。请记住即使在类的级别发生同步,一个重复的类加载失败还会根据你所处理的Java线程头发级别触发线程锁竞争。这种情况如果在JDK1.6中,当类加载实例级别进行同步时变得更加严重。

classNotFoundException
classNotFoundim2
因为这个原因,像JBoss WildFly 8  这样的Java EE容器会使用他们自身的并发类加载器来加载你的应用程序类。这此类加载器在更精细的粒度上实现了锁,因此可以并发的从同一个类加载器实例来加载不同的类。这同样与最新的JDK1.7+中改善性的支持多线程定制类加载器( Multithreaded Custom Class Loaders )保持一致。这种多线程定制类加载器可以一定程度上阻止类加载器死锁现象。 话虽然是这么说的。像java.* 还有Java EE容器模块这样系统级别的类,他们的类加载器还依赖于JDK默认的类加载器。这就意味着重复的类加载失败仍然会触发严重的线程锁竞争。这恰恰是下文我们要重现和演示的。

线程锁竞争– 问题复制

我们按照以下规范创建了一个简单应有程序,来重现和模拟这个问题

  • JAX-RS(REST)Web 服务采用一个假的类名“located”从系统包级别执

1String className =”java.lang.WrongClassName”;
2Class.forName(className);

这次模拟是采用20个JAX-RS Web service 线程来并发执行。 每一次调用都会有一个ClassNotFoundException. 为了减少对IO影响,我们禁用日志,并将关注点只放在类加载竟争上。

现在我们来看看JvisualVM中运行了30-60秒的结果。我们可以清晰的看到大量的BLOCKED线程等待在Object monitor 上获取锁。

minotor

分析JVM线程dump,可以清晰的暴露出问题:线程锁竞争。我们可以从JBoss将类的加载委托给JDK的ClassLoader的堆栈跟踪中看到。 为什么呢? 这是因为我们的错误的Java 类名被认为是系统类path的一部份。在这种情况下,JBoss将会把加载委托给系统类加载器,触发了针对那个特定类名的系统级同步,同时来自其它线程的waiters 等待获取一个锁来加载同样的类名。

许多线程等待获取 LOCK 0x00000000ab84c0c8…

01 "default task-15" prio=6 tid=0x0000000014849800 nid=0x2050 waiting for monitor entry [0x000000001009d000]
02java.lang.Thread.State: BLOCKED (on object monitor)
03  at java.lang.ClassLoader.loadClass(ClassLoader.java:403)
04  - waiting to lock <0x00000000ab84c0c8> (a java.lang.Object)
05  // Waiting to acquire a LOCK held by Thread “default task-20”
06  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
07  at java.lang.ClassLoader.loadClass(ClassLoader.java:356) // JBoss now delegates to system ClassLoader..
08  at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)
09  at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
10  at java.lang.Class.forName0(Native Method)
11  at java.lang.Class.forName(Class.java:186)
12  at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)
13  at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
14  at sun.reflect.GeneratedMethodAccessor15.invoke(Unknown Source)
15  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
16  at java.lang.reflect.Method.invoke(Method.java:601)
17……………………..
罪魁祸首的线程– default task-20

01"default task-20" prio=6 tid=0x000000000e3a3000 nid=0x21d8 runnable [0x0000000010e7d000]
02    java.lang.Thread.State: RUNNABLE
03                at java.lang.Throwable.fillInStackTrace(Native Method)
04                at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
05                - locked <0x00000000a09585c8> (a java.lang.ClassNotFoundException)
06                at java.lang.Throwable.<init>(Throwable.java:287)
07                at java.lang.Exception.<init>(Exception.java:84)
08                at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75)
09at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82) // ClassNotFoundException!                                      at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
10                at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
11                at java.security.AccessController.doPrivileged(Native Method)
12                at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
13                at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
14                - locked <0x00000000ab84c0e0> (a java.lang.Object)
15                at java.lang.ClassLoader.loadClass(ClassLoader.java:410)
16- locked <0x00000000ab84c0c8> (a java.lang.Object)   // java.lang.ClassLoader: LOCK acquired                                                             at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
17                at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
18                at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)
19                at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
20                at java.lang.Class.forName0(Native Method)
21                at java.lang.Class.forName(Class.java:186)
22                at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)
23                at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
24…………………………………

现在我们通过一个被标记为 “application”包中的一部分的Java 类为替换我们的类名,并在同样的条件下重新测试。


1String className =”org.ph.WrongClassName”;
2Class.forName(className);

test
正如我们所看到,不需要再应对BLOCKED线程.. 为什么这样呢?咱们一块看看JVM线程dump,更好的理解一下这种行为的变化。


01"default task-51" prio=6 tid=0x000000000dd33000 nid=0x200c runnable [0x000000001d76d000]
02    java.lang.Thread.State: RUNNABLE
03                at java.io.WinNTFileSystem.getBooleanAttributes(Native Method)    // IO overhead due to JAR file search operation
04                at java.io.File.exists(File.java:772)
05                at org.jboss.vfs.spi.RootFileSystem.exists(RootFileSystem.java:99)
06                at org.jboss.vfs.VirtualFile.exists(VirtualFile.java:192)
07                at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:127)
08                at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:124)
09                at java.security.AccessController.doPrivileged(Native Method)
10                at org.jboss.as.server.deployment.module.VFSResourceLoader.getClassSpec(VFSResourceLoader.java:124)
11                at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:252)
12                at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:76)
13                at org.jboss.modules.Module.loadModuleClass(Module.java:526)
14                at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:189)   // JBoss now fully responsible to load the class
15                at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:444) // Unchecked since using JDK 1.7 e.g. tagged as “safe” JDK
16                at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked(ConcurrentClassLoader.java:432)
17                at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:374)
18                at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)
19                at java.lang.Class.forName0(Native Method)
20                at java.lang.Class.forName(Class.java:186)
21                at org.jboss.tools.examples.rest.MemberResourceRESTService.AppCLFailure(MemberResourceRESTService.java:196)
22                at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.AppCLFailure(Unknown Source)
23                at sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source)
24……………….

上述堆栈跟踪信息表明:

  • 自从Java类名不再作为Java系统包的一部分,就不会有ClassLoader的委托,因此也不会有同步操作。
  • 自从JBoss认为JDK1.7+是个“安全”的JDK. ConcurrentClassLoader .使用LoadClassUnchecked()来实现 , 不会触发任何对象监控锁(Object monitor lock).
  • 没有同步就意味着不存在因为不间断ClassNotFoundException错误而导致的线程锁竞争。

注意在这种情况下JBoss做了大量工作来阻止线程锁竟争,由于过多的JAR文件查找操作和IO开销,重复的类加载尝试将一定程度上降低性能。要解决这样的问题需立即采取纠正措施。

结束语

我希望你喜欢这篇文章并对因为 过度的类加载操作而导致潜在的性能影响有进一步的理解。当JDK1.7 和现在的JAVA EE容器针对像死锁和线程锁竞争这样类加载问题上做出很大的提升时,潜在的问题仍然存在。因此,我强烈建议您密切监控你的应用程序运行情况、日志,并及时改正像java.lang.ClassNotFoundException 和java.lang.NoClassDefFoundError 这样的类加载错误. 

<think>好的,用户遇到了Java环境中找不到kotlin.jvm.internal.Intrinsics类的错误,具体是java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics。我需要根据提供的引用资料来找到解决方案。 首先,引用[3]提到,当出现这个错误时,可能是因为build.gradle中没有添加kotlin标准库依赖,或者应用Kotlin插件失败。用户需要检查是否在Gradle文件中正确配置了Kotlin插件和依赖。 步骤一应该是确认Kotlin插件是否应用。在模块的build.gradle中,应该添加apply plugin: 'kotlin-android',尤其是当模块中有Kotlin代码时。如果项目是纯Java的,但引用了Kotlin库,可能也需要这个插件? 接下来,检查依赖项。必须包含Kotlin标准库,例如org.jetbrains.kotlin:kotlin-stdlib-jdk8或者其他版本,比如jre7。用户需要根据项目使用的Kotlin版本选择合适的库,并确保版本号一致。 另外,引用[1]和[2]中的错误都是类找不到的情况,可能和依赖未正确引入或构建配置有关。用户可能还需要检查构建工具(如Maven或Gradle)是否正确下载了依赖,或者是否存在依赖冲突。 还要考虑构建配置中的其他问题,比如多模块项目中是否在所有需要的模块都添加了Kotlin依赖,或者是否在打包时遗漏了Kotlin库。例如,使用Spring Boot时可能需要将Kotlin库包含在最终的jar中,或者使用插件如kotlin-maven-plugin或kotlin-gradle-plugin来正确处理依赖。 此外,用户提到在Spring Boot中连接InfluxDB时出现此错误,可能需要检查InfluxDB的客户端库是否依赖于特定版本的Kotlin,导致版本不兼容。这时候需要确保所有Kotlin依赖版本一致。 最后,建议用户清理并重新构建项目,确保所有依赖都被正确下载和包含。如果问题仍然存在,可能需要检查IDE的设置,比如IntelliJ是否导入了所有Gradle更改,或者是否存在缓存问题。 总结解决方案的步骤应该是:确认插件应用、添加标准库依赖、检查版本一致性、清理构建,以及处理多模块或依赖冲突的问题。</think>### Java环境中找不到kotlin.jvm.internal.Intrinsics类的解决方案 #### 1. **确认Kotlin插件配置** - 在模块的`build.gradle`文件中,确保已添加Kotlin插件: ```groovy apply plugin: 'kotlin-android' // Android项目 // 或 apply plugin: 'kotlin' // 非Android项目 ``` 若未配置,会导致Kotlin代码无法编译,从而引发类找不到的问题[^3]。 #### 2. **添加Kotlin标准库依赖** - 在`build.gradle`的`dependencies`中添加Kotlin标准库(根据Kotlin版本选择): ```groovy implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // 或旧版本 implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" ``` 若未添加此依赖,运行时将因缺少Kotlin核心类而报错。 #### 3. **检查Kotlin版本一致性** - 确保项目中所有Kotlin相关依赖的版本一致。例如: ```groovy ext.kotlin_version = '1.6.21' // 全局定义版本号 implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" ``` 版本冲突可能导致部分类未被正确加载[^2]。 #### 4. **清理并重新构建项目** - 执行以下命令清理构建缓存: ```bash ./gradlew clean build --refresh-dependencies # Gradle项目 ``` 或通过IDE(如IntelliJ)执行`Clean Project`操作。 此操作可修复因依赖未完全下载或缓存导致的类缺失问题[^1]。 #### 5. **检查多模块配置** - 若项目包含多个模块,需在每个使用Kotlin的模块中重复步骤1-3。 例如,`common`模块依赖Kotlin库时,其`build.gradle`也需添加标准库。 #### 6. **确认打包包含Kotlin库** - 对于可执行JAR(如Spring Boot项目),需确保Kotlin库被包含在最终包中。 **Maven配置示例**: ```xml <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <requiresUnpack> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> </dependency> </requiresUnpack> </configuration> </plugin> ``` 此配置确保Kotlin类在运行时可用。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值