Java之NoClassDefFoundError错误

Posted by Codeboy on July 25, 2020

Java中的异常(含错误)主要包含ExceptionError 两种,这里简单的分析一下NoClassDefFoundError这个异常,JVM的类加载机制的委托行机制,决定了类加载器只加载一次,子类加载器不会再加载父类加载器已经加载过的类,在一些特定条件下,会出现编译时可以加载到类,运行时不可以加载到类,这时候就会出现 NoClassDefFoundError异常;与之相似的两个异常是ExceptionInInitializerErrorClassNotFoundException ,对比如下:

异常 说明
java.lang.ExceptionInInitializerError 类初始化失败,初始化中发生了异常;
java.lang.ClassNotFoundException 类本身不存在,第一次加载时就找不到;
java.lang.NoClassDefFoundError 1. 编译通过,运行时找不到; 2. 类已经加载过,再次使用时,发现并没有定义的类,原因是第一次类在创建的时候失败了,这里一般是静态变量、静态块执行失败造成类不能被创建;

复现代码

针对 NoClassDefFoundError 的两种出现的场景,我们来复现一下:

1. 编译通过,运行时找不到

public class A {
    public void test() {
        System.out.println("A");
    }
}
public class B {
    public static void main(String[] args) {
        new A().test();
    }
}

示例代码很简单,编译B,会产生A和B的class,删除A的class运行即可查看:

# 编译
javac B.java 

# 删除 A.class后运行
java B

## 执行结果
Exception in thread "main" java.lang.NoClassDefFoundError: A
	at B.main(B.java:3)
Caused by: java.lang.ClassNotFoundException: A
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 1 more

从错误中可以看出,在构建A的时候,classloader找不到A,所以抛出ClassNotFoundException 异常,进而引起了B类中的NoClassDefFoundError错误;

2. 静态变量等创建失败

public class C {

    public C() {
        throw new IllegalStateException("can't init");
    }

    public void test() {
    }
}
public class D {
    public static C instance = new C();
}
public class E {

    public static void main(String[] args) {
        try {
            D.instance.test();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        try {
            D.instance.test();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

编译E后输出如下:

java.lang.ExceptionInInitializerError
	at E.main(E.java:5)
Caused by: java.lang.IllegalStateException: can't init
	at C.<init>(C.java:4)
	at D.<clinit>(D.java:2)
	... 1 more
java.lang.NoClassDefFoundError: Could not initialize class D
	at E.main(E.java:10)

C类不能被创建,D中的静态变量instance在创建时异常,第一次jvm抛出了ExceptionInInitializerError,第二次使用D类时,由于上次D类因为instance 创建失败,进而引起了E类中的NoClassDefFoundError错误。

可以看到 NoClassDefFoundError 异常的发生通常是伴随类的创建失败,不论是ExceptionInInitializerError 或者ClassNotFoundException

修正保护

针对上述两种场景,该怎么进行保护呢?

第一种场景可以进行一些代码安全检查,同时增强代码测试覆盖率;

第二种场景可以优化代码,不使用静态方法,或者使用代理类进行保护,这里对D类进行优化:

public class D2 {
    public final static D2 instance = new D2();

    private static C cImpl = null;

    public void test() {
        if (cImpl == null) {
            synchronized (instance) {
                try {
                    cImpl = new C();
                } catch (Throwable t) {
                }
            }
        }
        if (cImpl != null) {
            cImpl.test();
        } else {
            // 兜底代码
            System.out.println("I am C'proxy");
        }
    }
}

将E中的 D.instance 换成D2.instance 即可,执行后正确进行到兜底逻辑。

这里更好的设计是将C中对外提供的方法抽出来一个接口,D2进行兜底实现,示例中重点在代理上面。

小结

NoClassDefFoundError 异常的发生通常是伴随类的创建失败,静态成员变量中尽量不要出现可能抛出异常的代码。

如有任何知识产权、版权问题或理论错误,还请指正。

转载请注明原作者及以上信息。