本文介绍了为什么在未使用的代码中包含对缺少的接口的方法调用的类会导致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:

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

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.

来自,

尽管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类加载错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

11-01 18:53