现在的位置: 首页 > 综合 > 正文

Java Puzzler-44 Cutting Class,为什么我没有 NoClassDefFoundError?

2018年10月10日 ⁄ 综合 ⁄ 共 2347字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.