今天又出现了NoClassDefFoundError这个错误,其实已经出现过很多次了,也知道了一般都是缺少相关Jar包之类的造成的,但是也没进行更深入的了解,今天我们就来稍微的探究一下这个错误到底怎么发生的,他和ClassNotFonudException这一常见异常又有什么区别呢。
我们知道这两个Java类都属于异常,那么我们首先来看看他们是checked exception还是unchecked exception。
我们看看ClassNotFonudException:
ClassNotFonudException->ReflectiveOperationException->Exception
很显然ClassNotFoundException是checkedException。
而NoClassDefFoundError
NoClassDefFoundError->LinkageError->Error
祖先是Error,不用说,是uncheckedException,而且是Error的子孙类,我们知道Error属于JVM等引发的不可挽救的错误,会直接让程序宕了。
我们再看看在Java API文档中是怎么定义他们的
ClassNotFonudException
当应用程序试图使用以下方法通过字符串名加载类时,但是没有找到具有指定名称的类的定义。抛出该异常:
- Class 类中的 forName 方法。
使用Classs.forName来加载类时,会进行初始化的工作,所以如果想仅仅是干干净净的加载类的话,最好还是用ClassLoader中的loadClass。当然如果你是使用
Class<?> forName(String name,
boolean initialize,
ClassLoader loader)
这个方法,且第二个参数为false时(参数见名知义),也不会进行初始化工作。
- ClassLoader 类中的 findSystemClass 方法。
此方法通过系统类加载器(参见 getSystemClassLoader())来加载该类
protected final Class<?> findSystemClass(String name)
throws ClassNotFoundException
{
ClassLoader system = getSystemClassLoader();
if (system == null) {
if (!checkName(name))
throw new ClassNotFoundException(name);
Class<?> cls = findBootstrapClass(name);
if (cls == null) {
throw new ClassNotFoundException(name);
}
return cls;
}
return system.loadClass(name);
}
getSystemClassLoader:获取系统类加载器的方法,那么什么是系统加载器呢,是Bootstrap这个祖先类加载器吗?不是的,系统类加载器是系统的入口点所使用的ClassLoader。一般是App ClassLoader。
Boootstrap->extsion->app classLoader,这三级类加载器大家应该都清楚作用吧,不清楚的话得好好补补基础啦。
- ClassLoader 类中的 loadClass 方法。
loadClass,很干净的加载类的方法。说到loadClass就有必要说我之前看过的自定义ClassLoader了。我们知道Java字节码文件是很容易被反编译的,我们常见的jd-gui反编译工具,反编译class文件是不是很方便。所以当我们的Java代码有安全性要求的时候,我们可以通过自定义ClassLoader来加密我们的Java代码(虽然一般不会用到)。那么自定义ClassLoader主要需要做些什么呢。我们先看loadClass方法的源码(源码版本均为JDK8)
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
其实如果你仔细看这个类的话,你会发现他给我们传递了很多信息。
- 首先,他告诉了我们ClassLoader的树形结构,从祖先类加载器开始,依次往下查找是否能够加载该类。
- 然后, 我们通读代码,发现,如果没有在JVM中的三个类加载成功加载的话,会执行 c = findClass(name);
这句话很关键,这句代码给了我们重写的空间。
实际上,我们实现我们自己定义的ClassLoader的话,只需要重写这个方法。然后我们需要在这个方法里调用defineClass方法,因为这个方法的作用是把字节码转化为Class对象(类加载的最终产品就是位于堆上的Class对象)。
扯了这么多好像还是没有讲出现这个异常的原因,其实简单来说:
ClassNotFoundException只可能出现在你的应用程序主动的装载类的过程中,可能是使用框架配置的初始化过程中,也有可能是程序中动态调用你认为已经写好的类中,但是很实际情况下,你在尝试调用一个实际上不存在的类,赶紧查一下是名称写错了,还是没有把他配置到正确的配置文件中吧。
扯的有点跑偏,最近喜欢把相关的东西结合在一起讲,搞得很多时候没有重点,很尴尬。好,对于ClassNotFountException的API文档就讲到这里,接下来看NoClassDeFoundError
NoClassDeFoundError
API文档是这样说的:
当 Java 虚拟机或 ClassLoader 实例试图在类的定义中加载(作为通常方法调用的一部分或者作为使用 new 表达式创建的新实例的一部分),但无法找到该类的定义时,抛出此异常。
当前执行的类被编译时,所搜索的类定义存在,但无法再找到该定义。
简单点的理解可以是这样:你如果编译了一个类B,在类A中调用,编译完成以后,你又删除掉B,运行A的时候那么就会出现这个错误(这就是缺少JAR包的情况),还有的情况可能是Classpath路径更改了也会造成这个问题。