作者:fredalxin\
地址:https://fredal.xin/classloade...

最近在做类隔离相干的一些工作,而恰好之前帮助开发同学时也发现会遇到许多类加载相干的异样,并且往往比拟难定位与解决。这里简略做一个小总结。

类加载

首先咱们来捋一捋类加载的基础知识。


以上是大家比拟相熟的类加载器模型,次要蕴含 3 品种加载器:

  • BootstrapClassloader 根加载器,也就是零碎类加载器,加载外围库,如 rt.jar。
  • ExtensionClassloader 扩大类加载器,次要加载/ext/上面的 jar 包
  • AppClassloader 离咱们最近的类加载器,负责加载 classpath 下的类,开发时候咱们的代码大部分由其加载。

此外咱们比拟须要晓得的几点:

  • 一个类是由 jvm 加载是通过类加载器+全限定类名确定唯一性的。
  • 双亲委派,家喻户晓,子加载器会尽量委托给父加载器进行加载,父加载器找不到再本人加载
  • 线程上下文类加载,为了满足 spi 等需要冲破双亲委派机制,当高层类加载器想加载底层类时通过 Thread.contextClassLoader 来获取以后线程的类加载器(往往是底层类加载器)去加载类。

ClassNotFoundException

ClassNotFoundException 示意类找不到异样,是一种 Exception,通常产生在载入阶段,当开发者被动调用 Class.forName()ClassLoader.loadClass()ClassLoader.findSystemClass()动静加载指定类时候,类加载器就会去 classpath 下寻找类,如果找不到就会抛出此谬误。

还有另外一种状况是当一个类曾经被某个类加载器加载到内存中,另外一个类加载器试图去加载时也会产生谬误。

ClassNotFoundException 是一个 exception 类,同时产生在被动执行动静加载时,所以咱们应该去 catch 它,避免产生一些运行时谬误。

NoClassDefFoundError

NoClassDefFoundError 是一种和 ClassNotFoundException 很像的谬误,只不过它是更重大的 error 类型。它产生在链接阶段,示意 jvm 在编译阶段能够找到相应的类,但在执行过程中却找不到相应的类。

一种起因是因为在编译后运行前类被更改或者删除了。另外一种则是 classpath 自身被批改过了,这能够通过System.getProperty("java.classpath")来找到程序理论运行的 classpath,或者通过-classpath 命令来指定正确的 classpath。

那如果是在 ide 中开发,很多时候呈现的状况是咱们能够通过 ide 编译通过,但在理论运行的 WEB-INF/lib 下却是没有的。所以排查的时候咱们须要去理论的 war 包上面确定是否有类。

NoSuchMethodError

咱们还会遇到 NoSuchMethodError 谬误,它示意找不到办法,但找不到办法归根结底是找到了不正确的类。

通常状况下是因为 jar 包抵触问题,即加载了不匹配版本的类导致的。例如利用中有 A、B 两个二方包,A 依赖 C-v1 包,而 B 依赖 C-v2 包,如果 maven 仲裁最初应用的是 C-v1 包,那么当 B 加载到 C-v2 中有而 C-v1 中没有的办法时就会报 NoSuchMethodError。

这种状况咱们首先得晓得 jvm 到底加载的是什么版本,这能够应用-verbose:class来确定。

LinkageError

LinkageError 相比拟之前几种谬误不那么常见,只有多个类加载器同时作用交互时才会呈现。

咱们晓得 jvm 中一个类由全限定类名与类加载器确定类实例,那么不同类加载器加载的同一个类是属于不同类实例的,而后在内存中如果两者产生交互,就会呈现 LinkageError 异样。

个别状况下,jvm 加载类都会遵循之前所述的双亲委派准则,不太可能呈现一个类有不同类加载器加载的状况。但在诸如 tomcat 之类的 javaEE 环境中,经常出这种情况,这是因为 tomcat 上的 web 利用类加载机制稍有不同,每个资源模块(比方一个 war 包)都优先应用本身的资源,冲破了双亲委派模型:

当 appClassLoader 加载类时候,会首先在本人的本地资源库中查找类,其次才会走双亲委派模型。那么如果一个类 A 由 AppClassLoaderx 加载,但其超类在 AppClassLoader 中没有,只有委托 CommonClassLoader 能力找到,当类 A 与其超类进行交互时就会报错了。

还有一种比拟常见的状况是进行自定义类加载器开发时遇到。比方开发类隔离容器时,冀望将某些中间件都由与利用不同的独立类加载器加载,但这时候如果中间件依赖 spring context,而利用自身也依赖 spring context,那么 作为 spring bean 交互时候就会妥妥报 LinkageError 了。

解决这个问题的方法包含 2 种,即管制不同类加载器加载的类不进行交互,或者都交于一个独特的父加载器进行加载。

Some Tips

总结一下以上几种谬误。ClassNotFoundException 以及 NoClassDefFoundError 都是因为加载不到类导致的,而 NoSuchMethodError 是因为加载了不正确的类,LinkageError 则是因为同一个类被多个类加载器加载所导致的。

以上这些问题都能够应用arthas进行排查。例如应用 sc 命令来查看 JVM 已加载的类信息,包含从哪个 jar 包读取,由哪个类加载器加载。应用 jad 命令来查看 jvm 中反编译的代码,能够定位到底到底有没有所需 method。以及应用 classloader 命令,来查看以后所有 classloader 的信息,包含加载的 urls,是否能加载到指定的类或者 resources 等。

近期热文举荐:

1.600+ 道 Java面试题及答案整顿(2021最新版)

2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!