Java解惑(即:Java Puzzlers: Traps, Pitfalls, and Corner Cases),作者 Joshua Bloch, Neal Gafter(Joshua Bloch就是 effective java和java concurrency in practice的作者)
第44条,为什么我就是搞不出来 NoClassDefFoundError,搜索了下,原因是 JDK6以上的 JVM的 Verifier 有了improvement(当然是好事,可以加快verification的速度。也就是,类可以更快的被加载)
从头到尾 总结下这个问题
public class Strange1 {
public static void main(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
public class Strange2 {
public static void main(String[] args) {
Missing m;
try {
m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
class Missing {
Missing() { }
}
这三个文件编译后,手动删除 Missing.class,然后运行,会有下面的结果
JDK5以及以下:
Strange1的结果是:NoClassDefFoundError
Strange2的结果是:Got it!
看字节码来找原因
两个类都:locals[0] == args
Strange1,locals[1],即用来存储 m,也用来存储 NoClassDefFoundError(这正是抛出错误的原因)
Strange2,locals[1],只用来存储 m,local[2]用来存储 NoClassDefFoundError(m的生命周期太长,所以,m和ex不能共用同一个 local[1])
详细分析 Strange1出错的原因
JVM 进行 flow analysis 时,verifier必须合并 local[1]包含的 类型(merge the types contained in local[1])。怎么merge呢?
计算这两个类的 first common superclass,也就是:the most specific superclass they share(即,Missing 和 NoClassDefFoundError 的 first common superclass)
locals[1]的状态:
从8直接到20时:Missing
从17到20时:NoClassDefFoundError
为了 compute 这两个的 first common superclass,verifier必须 load Missing 用于决定 Missing的superclass。这正是 抛出NoClassDefFoundError 的原因(这个Error是在 verificatioin阶段抛出的)。
一定要注意:当load完 Strange1后,进行 link-verify就抛出Error啦,这个时候,甚至还不存在运行的问题。
也就是说,Strange1改成下面这个样子,也还是会报NoClassDefFoundError,这时根本就没运行
public class Strange1 {
public static void notmain(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
public static void main(String[] args) {
System.out.println("Nothing related with Missing");
}
}
Strange2,verification时 不load Missing,等到 运行main时才 load Missing,所以,抛出的错误 就被 catch 住了
JDK6以及以上
统统都是:Got it!
在JDK1.6以及以上,是不会出现 NoClassDefFoundError 这个Error的,原因是:JDK6及以上的编译出来的类,存储了更多的 metadata
当寻找 Missing和NoClassDefFoundError的 first common superclass时,根本就无需 load Missing(从而,也不会 在verification时就抛出 NoClassDefFoundError)
所以,Strange1, Strange2 都在 运行 main 方法时才会抛出这个Error。从而,Strange1和Strange2 统统打印出 Got it
详细解释在这里:http://fupeg.blogspot.com/2007/07/java-puzzler.html(需翻墙),有意思的是:这篇文章下面,就是 Joshua Block和Neal Gafter 的comments
也可以使得 JDK6出现 NoClassDefFoundError,加一个 JVM 参数:java -XX:-UseSplitVerifier Strange1
JDK8
-XX:-UseSplitVerifier已经不支持了,即,搞不出来:NoClassDefFoundError
Java HotSpot(TM) Server VM warning: ignoring option UseSplitVerifier; support was removed in 8.0