ClassNotFoundException和NoClassDeFoundError

本文详细探讨了Java中NoClassDefFoundError与ClassNotFoundException的区别及发生原因。解释了这两种异常分别属于checkedException与uncheckedException,并通过代码示例展示了类加载过程。

今天又出现了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路径更改了也会造成这个问题。

<think>我们被要求解释JavaClassNotFoundExceptionNoClassDefFoundError的区别。根据引用资料,我们可以总结如下: 1. **异常类型**: - `ClassNotFoundException` 是一个检查异常(checked exception),继承自`Exception`,因此必须被捕获(try-catch)或在方法签名中声明抛出。 - `NoClassDefFoundError` 是一个错误(Error),继承自`Error`,属于不可查异常(unchecked),通常不需要也不建议捕获。 2. **发生时机**: - `ClassNotFoundException` 发生在运行时,当应用程序试图通过类的字符串名称加载类(例如使用`Class.forName()`, `ClassLoader.loadClass()`或`ClassLoader.findClass()`)但在类路径中找不到该类的定义时抛出。 - `NoClassDefFoundError` 发生在JVM的链接阶段(具体是解析阶段),当JVM尝试加载某个类(作为正常方法调用的一部分或者创建新实例时)但找不到该类的定义,而实际上这个类在编译时是存在的。这通常是因为类在编译时存在,但在运行时由于某种原因不可用(例如类文件被删除,或者静态初始化失败等)。 3. **原因**: - `ClassNotFoundException` 通常是由于类路径配置错误,动态加载类时找不到类。 - `NoClassDefFoundError` 通常是因为类初始化失败(如静态块或静态变量初始化时抛出异常),或者类文件在编译后发生了变化(如重新编译了某个类但没有重新编译依赖它的类)导致。 4. **处理方式**: - `ClassNotFoundException` 需要开发者通过try-catch捕获并处理,或者修改类路径配置确保类存在。 - `NoClassDefFoundError` 通常不应该被捕获,而是应该检查类路径以及类初始化代码(静态代码块静态变量初始化)是否有问题。 引用[5]给出了一个导致`NoClassDefFoundError`的例子:当一个类的静态变量初始化失败(如除以0),那么后续尝试使用这个类就会抛出`NoClassDefFoundError`。 下面我们按照要求组织答案,先结论再分析,并引用相关引用。</think>### JavaClassNotFoundException与NoClassDefFoundError的区别 #### ✅ 核心结论 **`ClassNotFoundException`是受检异常,发生在类主动加载失败时;`NoClassDefFoundError`是JVM错误,发生在类已存在但无法链接时**。两者在异常类型、触发时机处理方式上有本质区别[^1][^3][^4]。 --- ### 一、本质区别对比 | **特征** | **ClassNotFoundException** | **NoClassDefFoundError** | |------------------------|----------------------------------------------------|---------------------------------------------| | **继承关系** | 继承`java.lang.Exception`(受检异常)[^4] | 继承`java.lang.Error`(JVM错误)[^2] | | **触发场景** | 动态加载类失败(如`Class.forName()`)[^3] | 类已编译但运行时缺失依赖[^5] | | **发生阶段** | 运行时显式加载类时[^1] | JVM链接阶段(类解析时)[^3] | | **处理要求** | 必须用try-catch捕获或声明抛出[^4] | 不应捕获(JVM级错误)[^3] | | **典型原因** | 类路径缺失、拼写错误、动态加载失败[^1] | 静态初始化失败、类文件被篡改[^5] | --- ### 二、技术原理分析 #### 1. ClassNotFoundException(主动加载失败) 当**显式调用类加载方法**但找不到类定义时触发: ```java // 示例:动态加载不存在的类 try { Class<?> clazz = Class.forName("com.example.NonExistentClass"); // ❌ 触发异常 } catch (ClassNotFoundException e) { System.out.println("动态加载失败: " + e.getMessage()); // 需捕获 } ``` **根本原因**: - 类路径(Classpath)配置错误 - JAR包未正确引入[^1] - 类名拼写错误(如大小写不一致) #### 2. NoClassDefFoundError(被动链接失败) 当**JVM隐式链接类**时发现类定义缺失: ```java // 引用[5]示例:静态初始化失败导致后续加载崩溃 public class ClassWithInitErrors { static int data = 1 / 0; // ❌ 静态初始化抛出ArithmeticException } public class App { public static void main(String[] args) { // 首次尝试捕获异常 try { new ClassWithInitErrors(); } catch (Throwable t) { System.out.println("首次异常: " + t); // 输出: java.lang.ExceptionInInitializerError } // 二次加载触发NoClassDefFoundError new ClassWithInitErrors(); // ❌ 抛出NoClassDefFoundError } } ``` **根本原因**: - 类静态初始化失败(如静态块报错)[^5] - 编译后依赖类被删除或修改 - 类版本不兼容(编译与运行环境不一致) --- ### 三、关键差异图解 ```mermaid graph LR A[JVM加载类] --> B{是否显式加载?} B -->|Class.forName<br>ClassLoader.loadClass| C[ClassNotFoundException] B -->|new实例<br>调用静态成员| D{类是否已加载?} D -->|否| E[执行类加载] E --> F{静态初始化成功?} F -->|是| G[正常使用] F -->|否| H[NoClassDefFoundError] ``` --- ### 四、解决方案对比 | **异常类型** | **解决策略** | |------------------------|-------------------------------------------------| | **ClassNotFoundException** | 1. 检查类路径配置<br>2. 验证JAR包完整性<br>3. 确认类名拼写[^1] | | **NoClassDefFoundError** | 1. 检查静态初始化代码(static块)<br>2. 确保依赖类存在<br>3. 清理编译缓存[^5] | > 简单记忆: > - **ClassNotFoundException** → "找不到类" → 主动加载时缺失 > - **NoClassDefFoundError** → "类定义失效" → 被动链接时崩溃 ---
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值