方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还没有涉及到方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍最频繁的操作,但Class文件的编译过程不包含传统编译中的连接步骤,一切方法调用在Class文件里存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到了运行期间才能确定目标方法的直接引用。
解析
所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用。这种解析能成立的前提是:方法在程序真正运行前就有一个可确定的调用版本,并且该方法版本在运行期不可改变。满足这种“编译期可知,运行期不可变”的要求的方法,主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可访问。这种方法的调用就叫做“解析” Resolution。
与之对应的是,在Java虚拟机里面提供了5条指令调用字节码指令,分别如下:
invokestatic 调用静态方法
invokespecial 调用实例构造器<init>方法、私有方法和父类方法
invokevritual 调用所有的虚方法
invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic 先在运行时动态解析出调用点限定符所引用的方法,然后再执行此方法,在此之前 的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引用方法决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法,私有方法,实例构造器,父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,其他方法称为非虚方法(除了final方法)。
final修饰的方法,虽然是使用invokevirtual指令来调用,但由于它无法被覆盖,没有其他版本,因此也是非虚方法。
解析调用一定是个静态的过程,在编译期间就能完全确定,在类加载的解析阶段就会把涉及的符号引号全部转变为可确定的直接引用,不会延迟到运行期去完成。