由ClassNotFoundException和NoClassDefError到类加载过程的思考
- 以下是楼主的论证过程
- 场景1:使用Class.forName()去加载不存在的A类。
- 以下是NoClassDefError的场景
- 场景1:B类的class文件不存在,NoClassDefErrorTest类去编译
- 场景2:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件删除
- 场景3:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件删除
- 场景4:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件替换掉(其实就是模拟重现我们在各种导入第三方包时候出现的版本不兼容的情况)
结论:
鉴于很多人都是先看结论,然后从结论带入自己的思考去看过程的,所以我先把我自己的结论放这,如有不同的想法或者指出楼主的不合理的地方的,可以在下方评论区带上自己的想法和代码。
结论:
类的加载过程:加载,验证,准备,解析,初始化,使用,卸载七个步骤,其中验证、准备、解析3个部分统称为连接。具体作用,可以百度或者买书看,推荐《深入理解Java虚拟机:JVM高级特性与最佳实践》周志明著
ClassNotFoundException是在类加载第一个阶段,加载这个动作的时候,类加载器不能找到class文件,则会报ClassNotFoundException。比如在A中有段代码使用了Class.forName(B)去加载B,此时B的class文件不见了,则会报错ClassNotFoundException。它报这个错是说B的加载阶段出错了。(A类去加载B类,A类会报ClassNotFound B类)
NoClassDefError这个类是继承LinkageError(连接错误),所以也可以大概的猜出,NoClassDefError是在类加载的连接这个阶段报出来的。当A类中引用了B类,然后A类进行类的加载,加载成功后,然后进行接下来的连接阶段的时候,如果涉及到引用到B类却找不到B类的时候,就会报NoClassDefError。它报这个错是说A的连接阶段出错了。(A类去加载B类,A类会报B类NoClassDef)
以下是楼主的论证过程
场景1:使用Class.forName()去加载不存在的A类。
测试代码:
package com.jdk8.demo.classnotfounddemo;
public class ClassNotFoundTest {
@Test
public void testClassNotFound() throws ClassNotFoundException {
System.out.println(Class.forName("com.jdk8.demo.classnotfounddemo.B"));
}
}
结果:如果Class. forName的时候,不能找到B的class文件,则会报ClassNotFoundException,错误详情:
java.lang.ClassNotFoundException: com.jdk8.demo.classnotfounddemo.B
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
结果分析:一个ClassNotFoundTest类中运行B类,ClassNotFoundTest类在编译期间是没有B类的引用的,所以在 ClassNotFoundTest类的加载,验证,准备,解析阶段,都没有涉及到B类。所以可以通过编译,但是在运行ClassNotFoundTest时,就会去动态加载B类,则时候就会报错。所以我觉得ClassNotFoundException算是一个类加载错误,即只有加载类路径下不存在的类才会报的错。
以下是NoClassDefError的场景
场景1:B类的class文件不存在,NoClassDefErrorTest类去编译
测试代码:
package com.jdk8.demo.classnotfounddemo;
public class NoClassDefErrorTest {
public static void main(String[] args) {
System.out.println("11111");
}
public void b(){
System.out.println(new B());
}
}
NoClassDefErrorTest类编译失败,错误详情:
E:\juzix\IdeaProjects\practice\jdk8Demo\src\test\java\com\jdk8\demo\classnotfounddemo\NoClassDefErrorTest.java:16:32
java: 找不到符号
符号: 类 B
位置: 类 com.jdk8.demo.classnotfounddemo.NoClassDefErrorTest
结果分析:如果一个NoClassDefErrorTest类中运行B类,NoClassDefErrorTest类中有B类的引用。因为在NoClassDefErrorTest类编译的时候,B类是不存在的,并不能正确引用到的。则在编译NoClassDefErrorTest的时候,就会报“java: 找不到符号”,说明java文件编译成class文件的时候,会编译器javac会校验java代码中的引用之类的东西,如果校验不通过则编译不成功。进一步证明了,NoClassDefError是在编译通过后才会报的错,佐证了B类在NoClassDefErrorTest类编译的时候是存在的,但是运行的时候,B类不存在。
场景2:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件删除
代码详情:
NoClassDefErrorTest类
package com.jdk8.demo.classnotfounddemo;
public class NoClassDefErrorTest {
public static void main(String[] args) {
System.out.println("11111");
}
public void b(){
System.out.println(new B());
}
}
B类
package com.jdk8.demo.classnotfounddemo;
public class B{
}
NoClassDefErrorTest的main方法运行成功,未报错:
11111
结果分析:如果运行的时候未执行有关B类的代码,则不会报错,说明执行类的验证和解析的动作并不会一下执行完,并且没有去加载B类的字节码。这说明类的加载只有在真正执行了相关字节码的时候才会去加载。
场景3:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件删除
代码详情:
NoClassDefErrorTest类
package com.jdk8.demo.classnotfounddemo;
public class NoClassDefErrorTest {
public static void main(String[] args) {
System.out.println("11111");
a();
}
public void b(){
System.out.println(new B());
}
}
B类
package com.jdk8.demo.classnotfounddemo;
public class B {
}
NoClassDefErrorTest的main方法运行到一半的时候报错,错误详情:
11111
Exception in thread "main" java.lang.NoClassDefFoundError: com/jdk8/demo/classnotfounddemo/B
at com.jdk8.demo.classnotfounddemo.NoClassDefErrorTest.a(NoClassDefErrorTest.java:17)
at com.jdk8.demo.classnotfounddemo.NoClassDefErrorTest.main(NoClassDefErrorTest.java:13)
Caused by: java.lang.ClassNotFoundException: com.jdk8.demo.classnotfounddemo.B
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 2 more
结果分析:如果运行的时候执行有关B类的代码,则报错,说明执行B类相关的代码的时候,虚拟机会先去执行NoClassDefFoundError类的解析动作,在解析的时候,他发现在java进程的内存中没有B的定义(其实就是还没有加载B类到内存中),所以去进行B类的加载,然鹅B类已经被我删除,所以他先报ClassNotFoundException的异常,即B类的加载失败,随后就报NoClassDefErrorTest的解析错误即java.lang.NoClassDefFoundError的错误。
补充:其中B类在其他包也一样
package com.jdk8.demo.classnotfounddemo;
import com.jdk8.demo.compare.B;
public class NoClassDefErrorTest {
public static void main(String[] args) {
System.out.println(new B());
}
}
B类
package com.jdk8.demo.compare;
public class B {
}
错误信息:
Exception in thread "main" java.lang.NoClassDefFoundError: com/jdk8/demo/compare/B
at com.jdk8.demo.classnotfounddemo.NoClassDefErrorTest.main(NoClassDefErrorTest.java:14)
Caused by: java.lang.ClassNotFoundException: com.jdk8.demo.compare.B
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
场景4:B类的class文件,NoClassDefErrorTest类去编译,编译成功后再将B类的class文件替换掉(其实就是模拟重现我们在各种导入第三方包时候出现的版本不兼容的情况)
代码详情:
NoClassDefErrorTest类
package com.jdk8.demo.classnotfounddemo;
import com.jdk8.demo.compare.B;
public class NoClassDefErrorTest {
public static void main(String[] args) {
B b = new B();
System.out.println(a.test("1","2"));
}
}
NoClassDefErrorTest类编译前的B类:
package com.jdk8.demo.compare;
public class B {
public String test(String a,String b){
return "123";
}
}
NoClassDefErrorTest类编译后替换上去的B类:
package com.jdk8.demo.compare;
public class B {
public String test(String a){
return "123";
}
}
NoClassDefErrorTest报错,错误详情:
Exception in thread "main" java.lang.NoSuchMethodError: com.jdk8.demo.compare.B.test(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
at com.jdk8.demo.classnotfounddemo.NoClassDefErrorTest.main(NoClassDefErrorTest.java:15)
结果分析:结果有点意外,我以为会是NoClassDefError,结果是NoSuchMethodError,那看来是只有在NoClassDefErrorTest类编译成功,但运行时去解析B类相关的字节码的时候,没有加载到B类的情况下才会发生NoClassDefError。
附加内容:
有兴趣自己去测试体验以下的可以参考楼主的测试方法:
- 创建一个Maven项目
- 然后创建包和测试类去进行测试,然后点击运行测试类,这时候IDEA会自动将你的测试类NoClassDefErrorTest和依赖的B类进行编译,编译成功会放到target目录,然后第二次运行之前去这个目录删除B类的class文件就能产生你要的Exception和Error了。
- 另外,建议每次开始新一轮测试之前先把target目录删除,这样下次IDEA会判断你这个文件夹不存在,指定的class文件没有了,它会给你生成最新的class文件,保证测试的代码是正确的结果。