Java中的异常(含错误)主要包含Exception
和 Error
两种,这里简单的分析一下NoClassDefFoundError
这个异常,JVM的类加载机制的委托行机制,决定了类加载器只加载一次,子类加载器不会再加载父类加载器已经加载过的类,在一些特定条件下,会出现编译时可以加载到类,运行时不可以加载到类,这时候就会出现 NoClassDefFoundError
异常;与之相似的两个异常是ExceptionInInitializerError
和 ClassNotFoundException
,对比如下:
异常 | 说明 |
---|---|
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
异常的发生通常是伴随类的创建失败,静态成员变量中尽量不要出现可能抛出异常的代码。
如有任何知识产权、版权问题或理论错误,还请指正。
转载请注明原作者及以上信息。