方法调用
方法调用并不等同于方法中的代码被执行,方法调用阶段唯一的任务就是确定被调用方法的版本 (即调用哪一个方法),暂时还未涉及方法内部的具体运行过程。
解析
所有方法调用的目标方法在Class文件里面都是一个常量池中的符 号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能够成立的前 提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不 可改变的。换句话说,调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来。这类方法 的调用被称为解析(Resolution)。
在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要有静态方法和私有方法两 大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通 过继承或别的方式重写出其他版本,因此它们都适合在类加载阶段进行解析。
调用不同类型的方法,字节码指令集里设计了不同的指令。在Java虚拟机支持以下5条方法调用字 节码指令,分别是:
·invokestatic。用于调用静态方法。
·invokespecial。用于调用实例构造器<init>()方法、私有方法和父类中的方法。
·invokevirtual。用于调用所有的虚方法。
·invokeinterface。用于调用接口方法,会在运行时再确定一个实现该接口的对象。
·invokedynamic。先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
前面4 条调用指令,分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户设定的引 导方法来决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本, Java语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类方法4种,再加上被final 修饰的方法(尽管它使用invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引 用解析为该方法的直接引用。这些方法统称为“非虚方法”(Non-Virtual Method),与之相反,其他方 法就被称为“虚方法”(Virtual Method)。
解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号 引用全部转变为明确的直接引用,不必延迟到运行期再去完成。
分派
而另一种主要的方法调用形式:分派 (Dispatch)调用则要复杂许多,它可能是静态的也可能是动态的,按照分派依据的宗量数可分为单 分派和多分派。这两类分派方式两两组合就构成了静态单分派、静态多分派、动态单分派、动态多 分派4种分派组合情况。