为什么在未使用的代码中包含对缺失接口的方法调用的类会导致Java类加载错误?
我看到一些类加载行为似乎与JVM规范不一致,我想知道这是否是一个bug。如果不是,希望有人能解释原因 下面的示例代码只是从其主方法打印hello。它有一个未使用的方法,该方法包含对方法的方法调用,该方法声明它以“C”(接口)作为参数 执行main时(类路径中没有A、B和C)会为接口C抛出一个ClassNotFound错误(注意C在运行时实际上并不需要,因为它只在从不执行的方法中引用) 这似乎违反了JVM规范 Java虚拟机规范第2版第2.17.1节规定: 关于何时执行解析的唯一要求是解析过程中检测到的任何错误必须在程序中的某个点抛出,该点由程序执行某些操作,这些操作可能直接或间接要求链接到错误中涉及的类或接口 Java虚拟机规范第2版第2.17.3节规定: Java编程语言允许在链接活动(以及,由于递归,加载)发生时实现灵活性,前提是尊重语言的语义,在初始化类或接口之前完全验证和准备类或接口,并且在链接过程中检测到的错误被抛出到程序中的某个点,程序在该点上执行某些操作,这些操作可能需要链接到错误所涉及的类或接口 注意:如果我将定义上的参数类型更改为类而不是接口,那么代码将正确加载和执行为什么在未使用的代码中包含对缺失接口的方法调用的类会导致Java类加载错误?,java,classloader,Java,Classloader,我看到一些类加载行为似乎与JVM规范不一致,我想知道这是否是一个bug。如果不是,希望有人能解释原因 下面的示例代码只是从其主方法打印hello。它有一个未使用的方法,该方法包含对方法的方法调用,该方法声明它以“C”(接口)作为参数 执行main时(类路径中没有A、B和C)会为接口C抛出一个ClassNotFound错误(注意C在运行时实际上并不需要,因为它只在从不执行的方法中引用) 这似乎违反了JVM规范 Java虚拟机规范第2版第2.17.1节规定: 关于何时执行解析的唯一要求是解析过程中检
/**
*此版本失败,neverCalled()中的方法调用指向
*参数定义用于接口
*/
公共班机{
公共空间从不被调用(){
A=新的A();
B=新的B();//B实现C
//方法takeInter被声明为采用接口C类型的参数
//这段代码导致在Main时抛出ClassNotFound错误
//如果A、B和C不在类路径中,则加载
a、 takeInter(b);
}
公共静态void main(字符串[]args){
System.out.println(“你好…”);
}
}
/**
*此版本运行时,neverCalled()中的方法调用是对
*参数定义是针对类的
*/
公共班机{
公共空间从不被调用(){
A=新的A();
B=新的B();//B实现C
//方法takeInter被声明为采用接口C类型的参数
//这段代码导致在Main时抛出ClassNotFound错误
//如果A、B和C不在类路径中,则加载
a、 二级(乙);;
}
公共静态void main(字符串[]args){
System.out.println(“你好…”);
}
}
公共A类{
(B)在{}中的公共课;
公共空间(C in){}
}
公共类B实现C{}
公共接口C{}
埃德 我并不是故意想断章取义,而是把我认为相关的部分抽了出来。谢谢你帮助我理解这一点 不管怎么说,规格对我来说似乎很清楚。它说错误必须在一个点上抛出,而不是在一个点上抛出。当然,我在阅读了Java虚拟机内部第8章中的以下内容后阅读了VM规范,所以这可能影响了我的解释 从 如第7章“类的生命周期”所述,Java虚拟机的不同实现允许在程序执行期间的不同时间执行解析。一个实现可以选择通过跟踪来自初始类的所有符号引用,然后是来自后续类的所有符号引用,来提前链接所有内容,直到每个符号引用都被解析。在这种情况下,在调用其main()方法之前,应用程序将被完全链接。这种方法称为早期解决。或者,实现可以选择等到最后一分钟来解析每个符号引用。在这种情况下,Java虚拟机将仅在运行程序首次使用符号引用时解析该符号引用。这种方法称为延迟解决。实现也可以在这两个极端之间使用解决策略 尽管Java虚拟机实现在选择何时解析符号引用方面有一定的自由度,每个Java虚拟机都必须给人一种外部印象,即它使用的是后期解析。无论特定Java虚拟机何时执行其解析,它始终会抛出由于在程序执行过程中第一次实际使用符号引用时尝试解析符号引用而导致的任何错误。这样,在用户看来,它总是像是延迟了解析一样。如果Java虚拟机执行早期解析,并且在早期解析期间发现类文件丢失,它将不会通过抛出适当的错误来报告类文件丢失,直到稍后在程序中实际使用该类文件中的某些内容。如果程序从未使用过该类,则永远不会抛出错误
下面是一个更简单的例子,它也失败了
public class Main {
public void neverCalled() {
A a = new A();
B b = new B();
a.takeInter(b);
}
public static void main(String[] args) {
System.out.println("Hello...");
}
}
class A {
public void takeInter(A in) {
}
}
class B extends A {
}
class C {
}
在字节码中
public void neverCalled();
Code:
0: new #2 // class A
3: dup
4: invokespecial #3 // Method A."<init>":()V
7: astore_1
8: new #4 // class B
11: dup
12: invokespecial #5 // Method B."<init>":()V
15: astore_2
16: aload_1
17: aload_2
18: invokevirtual #6 // Method A.takeInter:(LA;)V
21: return
你对的引用是断章取义的。下面是粗体字。在上下文中读取时,很明显,“错误…必须在程序中的某个点抛出…”意味着“错误…必须在程序到达某个点时抛出…”。这句话本身就可以用词
$ rm A.class B.class C.class
$ java -Xverify:none -cp . Main
Hello...
$ java -cp . Main
Exception in thread "main" java.lang.NoClassDefFoundError: A