为什么在未使用的代码中包含对缺少的接口的方法调用的类会导致Java类加载错误? [英] Why does a class containing a method call to a missing Interface within unused code cause a Java class loading error?

查看:55
本文介绍了为什么在未使用的代码中包含对缺少的接口的方法调用的类会导致Java类加载错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到某些类加载行为似乎与JVM规范不一致,并且想知道这是否是错误。否则,希望有人能解释原因。

I'm seeing some class loading behavior that appears to be inconsistent with the JVM spec and am wondering if this is a bug. Or if not, hoping someone can explain why.

下面的示例代码只是从其main方法中打印问候。它有一个未使用的方法,其中包含对方法的方法调用,该方法声明该方法以'C'(这是一个接口)作为参数。

The example code found below simply prints hello from its main method. It has an unused method that contains a method call to a method that declares it takes a 'C' (which is an interface) as an argument.

在执行main时 (在类路径中没有A,B和C)。对于接口C抛出ClassNotFound错误。(注意C在运行时实际上并不需要,因为它仅在永不执行的方法中被引用)。

When the main is executed (without A, B, and C in the class path) a ClassNotFound Error is thrown for Interface C. (Note C is never actually needed at runtime since it is only referenced in a method that never executes).

这似乎违反了JVM规范

This appears to be a violation of the JVM specification

Java VM Spec第27.1版的第2.17.1节说:

Section 2.17.1 of the Java VM Spec, 2nd edition says:


关于何时执行解析的唯一要求是在解析期间检测到的任何错误都必须抛出到程序中某个位置,该位置程序可能会采取某些措施,直接或间接地,需要链接到错误所涉及的类或接口

Java VM Spec,第二版的第2.17.3节说:

Section 2.17.3 of the Java VM Spec, 2nd edition says:


Java编程语言al降低了链接活动(以及由于递归,加载)发生的时间的实现灵活性,前提是要尊重语言的语义,在初始化类或接口之前对其进行完全验证和准备,并且在链接过程中检测到的错误被抛出到程序中某个位置,在该位置程序执行了一些可能需要链接到错误所涉及的类或接口的操作

注意:如果我将定义上的参数类型更改为类而不是接口,则代码将正确加载并执行。

Note: If I change the type of the parameter on the definition to a class instead of an interface then the code loads and executes correctly.

/**
 * This version fails, the method call in neverCalled() is to a method whose 
 * parameter definition is for an Interface
 */
public class Main {

    public  void neverCalled(){
          A a = new A();
          B b = new B(); // B implements C
      
          //method takeInter is declared to take paramters of type Interface C
          //This code is causes a ClassNotFound error do be thrown when Main
          //is loaded if A, B, and C is not in the class path
          a.takeInter(b); 
    }

    public static void main(String[] args) {
        System.out.println("Hello...");
    }
}


/**
 * This version runs, the method call in neverCalled() is to a method whose 
 * parameter definition is for a Class
 */
public class Main {

    public  void neverCalled(){
          A a = new A();
          B b = new B(); // B implements C
      
          //method takeInter is declared to take paramters of type Interface C
          //This code is causes a ClassNotFound error do be thrown when Main
          //is loaded if A, B, and C is not in the class path
          a.takeClass(b); 
    }

    public static void main(String[] args) {
        System.out.println("Hello...");
    }
}


public class A {
    public void takeClass(B in){};
    public void takeInter(C in){}
}

public class B implements C {}

public interface C {}




Ed,


Ed,

我不是故意地尝试引用上下文时的报价我撤出了我认为是相关部分的内容。谢谢您帮助我尝试理解这一点。

I wasn't intentionally trying to take the quote out of context I pulled out what I thought was the relevant part. Thank you for helping me try to understand this.

无论如何,该规范对我来说似乎很清楚。它说错误必须扔到某个地方而不是一个地方。当然,在阅读《 Java虚拟机内部》第8章中的以下内容之后,我阅读了VM规范,所以也许使我的解释更加生动。

Anyhow, the spec seems pretty clear to me. It says the errors must be thrown at a point not by a point. Granted I read the VM spec after reading the following in Chapter 8 of Inside The Java Virtual Machine, so maybe that colored my interpretation.

来自, http://www.artima.com/insidejvm/ed2/linkmod.html


如第7章类的生命周期中所述,允许Java虚拟机的不同实现在程序执行期间的不同时间执行解析。一个实现可以选择先链接所有内容,方法是遵循初始类中的所有符号引用,然后遵循后续类中的所有符号引用,直到解析完每个符号引用为止。在这种情况下,应用程序将在其main()方法被调用之前被完全链接。这种方法称为早期解决方案。或者,实现可以选择等到最后一分钟解析每个符号引用。在这种情况下,只有在运行的程序首次使用Java虚拟机时,它才会解析符号引用。这种方法称为后期解析。实现也可以在这两个极端之间使用解析策略。

As described in Chapter 7, "The Lifetime of a Class," different implementations of the Java virtual machine are permitted to perform resolution at different times during the execution of a program. An implementation may choose to link everything up front by following all symbolic references from the initial class, then all symbolic references from subsequent classes, until every symbolic reference has been resolved. In this case, the application would be completely linked before its main() method was ever invoked. This approach is called early resolution. Alternatively, an implementation may choose to wait until the very last minute to resolve each symbolic reference. In this case, the Java virtual machine would resolve a symbolic reference only when it is first used by the running program. This approach is called late resolution. Implementations may also use a resolution strategy in-between these two extremes.

尽管Java虚拟机实现可以自由选择何时解析符号引用,但每个Java虚拟机都必须给人的印象是它使用了较晚的分辨率。无论特定的Java虚拟机何时执行其解析,它始终会在程序执行中首次实际使用符号引用的位置抛出因尝试解析符号引用而导致的任何错误。 。这样,它将始终向用户显示,好像分辨率晚了。如果Java虚拟机进行了早期解析,并且在早期解析过程中发现缺少类文件,则直到程序中稍后实际使用该类文件中的某些东西时,它才会抛出适当的错误来报告该类文件丢失。如果程序从不使用该类,则将永远不会引发错误。

Although a Java virtual machine implementation has some freedom in choosing when to resolve symbolic references, every Java virtual machine must give the outward impression that it uses late resolution. No matter when a particular Java virtual machine performs its resolution, it will always throw any error that results from attempting to resolve a symbolic reference at the point in the execution of the program where the symbolic reference was actually used for the first time. In this way, it will always appear to the user as if the resolution were late. If a Java virtual machine does early resolution, and during early resolution discovers that a class file is missing, it won't report the class file missing by throwing the appropriate error until later in the program when something in that class file is actually used. If the class is never used by the program, the error will never be thrown.


推荐答案

这是一个更简单的示例,该示例也会失败。

Here is a simpler example which also fails.

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   

b 隐式转换为 A ,它似乎需要检查。

The b is implicitly cast to an A and it appears to need to check this.

如果您关闭所有验证功能,则不会发生错误。

If you turn all verification off, no error occurs.

$ rm A.class B.class C.class 
$ java -Xverify:none -cp . Main
Hello...
$ java -cp . Main
Exception in thread "main" java.lang.NoClassDefFoundError: A

这篇关于为什么在未使用的代码中包含对缺少的接口的方法调用的类会导致Java类加载错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆