Java VM何时尝试加载Class依赖项?

时间:2011-11-17 22:17:09

标签: java classloader

我遇到了一个我不明白的类加载器问题。我在使用Java 1.6.0和Windows XP的OSX上看到过相同的行为。

当我运行以下代码且MyListenerMyObject不在类路径中时,我得到NoClassDefFoundError。但是,如果我删除MyObject.add(my)行或将其替换为MyObject.add(null),则代码运行正常。

请注意,实际上从未使用过具有无法解析的依赖项的方法。

我不明白为什么MyObject.add(my)会导致虚拟机尝试加载MyListener,但MyListener my = new MyListener(){};却没有。

public class Main {

    public  void neverCalled(){
        MyListener my = new MyListener(){};
        MyObject.add(my);
    }

    public static void sayHi(){
        System.out.println("Hello");
    }

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

MyObjectMyListener

没有任何意义
public class MyObject {
    public static void add(MyListener in){}
}

public interface MyListener {}

我根据下面的标准提供的信息做了一些额外的研究。显然,由于某些未知原因,使用参数进行方法调用似乎会导致加载参数类,而仅仅声明变量则不会。

Java VM Spec第2.17.1节第2版说:

  

关于何时执行解析的唯一要求是解析期间检测到的任何错误必须抛出程序中的某个点,程序可能会直接或间接地需要链接到该类或错误涉及的界面

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

  

Java编程语言允许实现灵活性,以便何时发生链接活动(以及由于递归,加载),前提是语言的语义得到尊重,类或接口在其之前完全验证和准备初始化,并且在链接期间检测到的错误被抛出到程序中的某个点,程序可能需要链接到错误中涉及的类或接口

最后,内部Java虚拟机的第8章说:

  

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

     

尽管Java虚拟机实现在选择何时解析符号引用方面有一定的自由度,但每个Java虚拟机都必须给人以外观的印象:它使用后期解析。无论何时特定的Java虚拟机执行其解析,它总是会抛出任何错误,这些错误是在程序执行时尝试解析符号引用时产生的,这是第一次实际使用符号引用即可。通过这种方式,用户将始终看起来好像分辨率很晚。如果Java虚拟机提前解析,并且在早期解析期间发现缺少类文件,则在实际使用该类文件中的某些内容时,它将不会通过抛出相应的错误来报告该类文件。如果程序从不使用该类,则永远不会抛出错误。

从表面上看,我看到的行为似乎违反了JVM规范。

3 个答案:

答案 0 :(得分:2)

我已经测试过了。当它是MyObject.add(my);时,只需要MyListener而不是MyObject。 令人惊奇的是:当我用MyObject.add(my);替换System.out.println(my);时,没有出现任何错误。唯一不同的是静态方法println()的参数类型是Object,而不是MyListener。

我经常搜索并找到一些有用的信息。 让我们看看以下单词,它来自Inside the Java2 Virtual Machine

类加载器(引导程序或用户定义的)在加载类型之前不需要等到类型的第一次活动使用。类加载器可以在预期最终使用时尽早缓存类型的二进制表示,加载类型,或者在相关组中加载类型。但是,如果类加载器在早期加载期间遇到问题,则它必须仅在类型的第一次活动使用时报告该问题(通过抛出LinkageError的子类)。换句话说,如果类加载器在早期加载期间遇到丢失或格式错误的类文件,它必须等待报告该错误,直到该类首次由程序主动使用。如果程序从不主动使用该类,则类加载器将永远不会报告错误。

前半部分可以回答为什么有NoClassDefFoundError。由于JVM可以自己决定何时加载类,也许

    MyListener my = new MyListener(){};
    MyObject.add(my);

这样的样式只会使它加载MyListener接口。

但下半场似乎与此相冲突。实际上永远不会调用neverCalled方法,没有主动使用。我认为唯一的原因可能是java1.2规范。

答案 1 :(得分:0)

我在一个软件包中测试了你的代码,它运行正常。

答案 2 :(得分:0)

MyObject.add方法是静态的,因此必须在加载Main对象时加载,以便在加载Main类之前调用​​静态初始值设定项。另一方面,MyListener对象可以在第一次使用时初始化静态初始值设定项,因为代码可以看到MyListener类上没有任何GET_STATIC或PUT_STATIC调用。