在web项目中的某一个请求中使用到了一个工具类来进行某些操作。但是在访问这个请求的时候发现一直报错,提示
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
[Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class com.example.demo.StaticTest] with root cause
java.lang.NoClassDefFoundError: Could not initialize class com.example.demo.StaticTest
其中com.example.demo.StaticTest就是出现问题的类。根据日志,提示StaticTest发生了NoClassDefFoundError异常,也就是这个类不存在。但是我代码中明明有这个类,为啥会提示不存在呢?
原因是我在这个类中使用static块初始化了一些数据,而恰恰在初始化这些数据的时候发生了异常,导致该类并没有被成功的加载到jvm中。但是我隐约记得,如果static发生了异常应该会卡主整个程序才对,为啥其他业务一切正常,只有该请求会报错。于是我编写了如下这么一个类并运行main方法测试。
public class StaticTest {
private static Integer num;
static{
num = 100 / 0;
}
public static Integer getNum() {
return num;
}
public static void main(String[] args) {
System.out.println(StaticTest.getNum());
System.out.println(StaticTest.getNum());
System.out.println(StaticTest.getNum());
}
}
其中我使用100/0模拟发生异常操作。并在main方法中运行了3次getNum()方法。发现在执行第一个getNum方法的时候程序就终止了。
Connected to the target VM, address: '127.0.0.1:54319', transport: 'socket'
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ArithmeticException: / by zero
at com.example.demo.StaticTest.<clinit>(StaticTest.java:8)
Disconnected from the target VM, address: '127.0.0.1:54319', transport: 'socket'
Process finished with exit code 1
跟我实际碰到的不太一样,于是我又编写了一个简单的controller,在controller中的请求方法中调用getNum方法,使用web来访问请求。
在第一次访问请求的时候,会抛出java.lang.ExceptionInInitializerError
异常
在后续n次访问请求的时候,则会抛出java.lang.NoClassDefFoundError
异常
这次发生的结果就跟我一开始碰到的一样了。但是为啥只有第一次是抛出类初始化异常,而后续全部是抛出类不存在的异常呢?经过查阅资料学习到,类只会被加载一次,加载失败之后再次调用就会抛出类不存在的异常。问题找到了,所以千万不要在static块中做可能发生异常的操作,如果有也要用try catch处理掉,不要直接不处理或者抛出异常。
以下是查阅到的相关资料:
NoClassDefFoundError和ClassNotFoundException区别
1)NoClassDefFoundError发生在JVM在动态运行时,根据你提供的类名,在classpath中找到对应的类进行加载,但当它找不到这个类时,就发生了java.lang.NoClassDefFoundError的错误
2)ClassNotFoundException是在编译的时候在classpath中找不到对应的类而发生的错误。
3)ClassNotFoundException比NoClassDefFoundError容易解决,是因为在编译时我们就知道错误发生,并且完全是由于环境的问题导致。
类加载的特性
在JVM的生命周期里,每个类只会被加载一次。
类加载的原则
延迟加载,能少加载就少加载,因为虚拟机的空间是有限的。
类加载的时机
1)第一次创建对象要加载类。
2)调用静态方法时要加载类,访问静态属性时会加载类。
3)加载子类时必定会先加载父类。
4)创建对象引用不加载类。
5)子类调用父类的静态方法时:当子类没有覆盖父类的静态方法时,只加载父类,不加载子类;当子类有覆盖父类的静态方法时,既加载父类,又加载子类。
6)访问静态常量,如果编译器可以计算出常量的值,则不会加载类。例如:public static final int a =123;
否则会加载类,例如:public static final int a = math.PI
。