[讨论] [JVM] 一个关于NoSuchMethodError的疑问

bolinyang 2014-12-06
JVM 先去加载ClassX,由于ClassX中调用了ClassY中的方法f(),因此JVM会先去加载ClassY,但是加载了一个ClassY,这个ClassY中不存在方法f,此时会抛NoSuchMethodError吗?还是正真调用ClassY中的f方法时才抛NoSuchMethodError。除了写例子去验证,如何从JVM源码中分析?
RednaxelaFX 2014-12-07
我觉得这个问题符合为啥别读HotSpot VM的源码的范畴。

楼主首先要解决的是概念问题,是在JVM规范层面可以解决的问题,而不需要细到具体的JVM的实现细节。

根据JVM规范,JVM加载一个类的有三个步骤:load, link, initialize。
其中load动作可以任意早执行,但是不可以过早报ClassNotFoundException / NoClassDefFoundError——这些异常最早只能在initialize的时机抛出。对Java程序员来说,JVM需要维持一种late loading的表象,无论内部实际的加载时机如何。
Link动作有一部分可以延迟进行。
Initialize动作最早只能在Java程序第一次主动使用某个类的时候才可以执行。主动使用包括new、getstatic/putstatic、invokestatic和Class.forName(name, true /* initialize */, loader)等。

加载一个类并不会导致它所依赖的所有类都被递归加载(JVM可以选择递归把依赖的类都load起来,但这不是必要的)。这里所谓“依赖”指的是在某个类A的常量池里有对某个别的类B的引用——这覆盖了类A在字段、方法里使用了类B的所有情况。
在尝试resolve某个依赖的时候,该依赖涉及的类才会被加载。所以说如果类A有一个字段是B类型的,那么加载A并不会导致B被加载;同理,如果A.foo()里使用了类B(例如new B()),这也不会使类A加载的时候就加载类B。

JVM要求要在加载一个类A之前完成加载其直接父类/直接父接口。如果加载的类型是一个类(而不是接口),那么在加载它的link阶段需要把它的直接父类和直接父接口load并link起来(JVMS7 5.3.5 step 3)。

所以楼主问题中:
bolinyang 写道
JVM 先去加载ClassX,由于ClassX中调用了ClassY中的方法f(),因此JVM会先去加载ClassY,但是加载了一个ClassY,这个ClassY中不存在方法f,此时会抛NoSuchMethodError吗?

这个“由于…因此”的因果关系不成立。类X如果有方法调用了Y.f(),那只是让类X对类Y有一般依赖,并不会在加载X的过程中导致类Y被加载。

这描述的时间线是:
类X被加载
-> 类X加载完成,此时类Y如果不是X的父类、而只是一般的依赖的话,就不一定会被加载
-> 有代码调用了Y.f(),再它能真的执行Y.f()之前:
  -> 类Y被加载完成。
  -> 然后对Y.f()进行resolution,此时发现类Y上没有名为f而且signature匹配的方法。于是抛出NoSuchMethodError。
nijiaben 2014-12-09
就hotspot实现而言,在做字节码校验的时候其实是有可能对部分依赖做加载的,比如stackmap里指定的类型
Global site tag (gtag.js) - Google Analytics