1.概述
① 方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
② 一切方法调用在Class文件里面存储的都只是常量池中的符号引用,而不是方法在实际运行时内存布局的入口地址(即直接引用);
③ 优点:更强大的动态扩展能力;
④ 缺点:使java方法调用过程复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用;
2.解析
① 解析成立前提条件:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可变的;
② 定义:调用目标在程序代码写好、编译器执行编译时就必须确定下来,这类方法的调用称为解析;
③ “编译期可知,运行期不变”,符合此要求的主要包括静态方法和私有方法,它们都不可能通过继承或别的方式重写其他版本,因此他们都适合在类加载阶段进行解析;
④ java虚拟机提供5条方法调用字节码指令:invokestatic(调用静态方法)、invokespecial(调用实例构造器<init>方法、私有方法和父类方法)、invokevirtual(调用所有的虚方法)、invokeinterface(调用接口方法,会在运行时再确定一个实现此接口的对象)、invokedynamic(先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法)
⑤ 以上前4条调用指令,分派逻辑是固化在jvm内部的,而最后一个的分派逻辑是由用户所设定的引导方法决定的;
⑥ 非虚方法:只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法四类,他们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法被称为非虚方法,与之相反,其他方法称为虚方法(除去final方法);
⑦ final修饰的方法:也是非虚方法,使用invokevirtual指令调用,无法被覆盖,无其他版本,无需对方法接收者进行多态选择(或者说多态选择结果唯一);
⑧ 解析一定是个静态的过程,编译期就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期去完成;
3.分派
① 分派调用可能是静态的,也可能是动态的,根据分派依据的宗量数可分为单分派和多分派,这两类分派方式的两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派4种分派组合情况;
② 分派调用过程揭示java多态性特征的一些最基本体现,如重载和重写的实现;
③ 静态分派:所有依赖静态方法来定位方法执行版本的分派动作称为静态分派。其典型应用是方法重载,发生在编译阶段;
④ 重载优先级:以字符(‘a’)为例,优先级由高到低为char-int-long-Character-Serializable-Object-char...,注意最后一个是不定长参数;
⑤ 动态分派:与多态性的另一个重要体现——重写有关,我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派;
⑥ 宗量:方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种,单分派是根据一个宗量对目标方法进行选择,多分派则是根据多余一个宗量对目标方法进行选择;
⑦ java语言的静态分派属于多分派类型,动态则是单分派(未发布JDK1.8时,JDK1.7新增的invokedynamic指令是方法调用指令中最复杂的一条);(书中有示例)
⑧ 虚拟机动态分派的实现:最常用的“稳定优化”手段就是为类在方法区中建立一个虚方法表,使用虚方法表索引来代替元数据查找以提高性能。除此之外还会使用内联缓存和基于“类型继承关系分析”技术的守护内联两种非稳定的“激进优化”手段来获得更高的性能;
4.动态类型语言支持(JDK7的invokedynamic指令)
① 动态类型语言特征:类型检查的主体过程是在运行期而不是编译期,相对的,在编译期就进行类型检查过程的语言(C++和java等)就是最常用的静态类型语言;
② JDK1.7与动态类型
③ java.lang.invoke包
④ invokedynamic指令:每一处含有此指令的位置都称作动态调用点