Jvm(67),方法调用----单分派与多分派

方法的接收者与方法的参数统称为方法的宗量,这个定义    早应该来源于《Java与模式》一书。根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。

单分派和多分派的定义读起来拗口,从字面上看也比较抽象,不过对照着实例看就不难理解了。代码清单8-10中列举了一个Father和Son一起来做出"一个艰难的决定"的例子。


package
demo.jvm.test8?

public
class
Demo5 {
static
class
QQ {}

static
class
_360 {}

//重载,静态分派调用

public
static
class
Father {

public
void
hardChoice(QQ arg) {

System.out.println("father choose qq")?

}

public
void
hardChoice(_360 arg) {

System.out.println("father choose 360")?

}

}

//重写,动态分派调用

public
static
class
Son extends
Father {

public
void
hardChoice(QQ arg) {

System.out.println("son choose qq")?

}

public
void
hardChoice(_360 arg) {

System.out.println("son choose 360")?

}

}

public
static
void
main(String[] args) {

Father father = new
Father()?


Father son = new
Son()?

father.hardChoice(new
_360())?

son.hardChoice(new
QQ())?

} }

father choose 360//重载,静态 son choose qq//重写,动态

在main函数中调用了两次hardChoice()方法,这两次hardChoice()方法的选择结果在程序输出中已经显示得很清楚了。

我们来看看编译阶段编译器的选择过程,也就是静态分派的过程。这时选择目标方法的依据有两点:一是静态类型是Father还是Son,二是方法参数是QQ还是360。这次选择结果的终产物是产生了两条invokevirtual指令,两条指令的参数分别为常量池中指向

Father.hardChoice(360)及Father.hardChoice(QQ)方法的符号引用。因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。

再看看运行阶段虚拟机的选择,也就是动态分派的过程。在执

行"son.hardChoice(newQQ())"这句代码时,更准确地说,是在执行这句代码所对应的invokevirtual指令时,由于编译期已经决定目标方法的签名必须为

hardChoice(QQ),虚拟机此时不会关心传递过来的参数"QQ"到底是"腾讯QQ"还是"奇瑞QQ",因为这时参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型是Father还是Son。因为只有一个宗量作为选择依据,所以Java语言的动态分派属于单分派类型。

根据上述论证的结果,我们可以总结一句:今天(直至还未发布的Java 1.8)的Java语言是一门静态多分派、动态单分派的语言。强调"今天的Java语言"是因为这个结论未必会恒久不变,C#在3.0及之前的版本与Java一样是动态单分派语言,但在C#4.0中引入了dynamic 类型后,就可以很方便地实现动态多分派。

虚拟机动态分派的实现

前面介绍的分派过程,作为对虚拟机概念模型的解析基本上已经足够了,它已经解决了虚拟机在分派中"会做什么"这个问题。但是虚拟机"具体是如何做到的",可能各种虚拟机的实现都会有些差别。由于动态分派是非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此在虚拟机的实际实现中基于性能的考虑,大部分实现都不会真正地进行如此频繁的搜索。面对这种情况, 常用的"稳定优化"手段就是为类在方法区中建立一个虚方法表(Vritual Method Table,也称为vtable,与此对应的,在

invokeinterface执行时也会用到接口方法表——Inteface Method Table,简称itable),使用虚方法表索引来代替元数据查找以提高性能。我们先看看代码清单8-10所对应的虚方法表结构示例,如图8-3所示。

虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口。如果子类中重写了这个方法,子类方法表中的地址将会替换为指向子类实现版本的入口地址。

图8-3中,Son重写了来自Father的全部方法,因此Son的方法表没有指向Father类型数据的箭头。但是Son和Father都没有重写来自Object的方法,所以它们的方法表中所有从

Object继承来的方法都指向了Object的数据类型。

这就类似于我们平常用的tostring的方法,经常这样用但是从来不考虑它的原理,这里就是说只要子类不重写tostring的方法,那么就会直接调用父类的方法。为了程序实现上的方便,具有相同签名的方法,在父类、子类的虚方法表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需的入口地址。

方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。

原文地址:https://www.cnblogs.com/qingruihappy/p/9691690.html

时间: 2024-08-29 20:55:27

Jvm(67),方法调用----单分派与多分派的相关文章

关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理

JVM中相关方法的调用的指令 invokestatic 调用静态方法. invokespecial 用于调用构造器方法<init>.私有方法.父类方法. invokevirtual 用于调用类的所有虚方法. invokeinterface 用于调用接口方法. 解析(resolution)与分派(dispatch) 解析 解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变 为可确定的直接引用,不会延迟到运行期再去完成. 下面我们看一段代码: /**

JVM 方法调用之静态分派

分派(Dispatch)可能是静态也可能是动态的,根据分派依据的宗量数可分为单分派和多分派.这两种分派方式的两两组合就构成了静态单分派,静态多分派,动态单分派,动态多分派这4种组合.本章讲静态分派. 1.静态分派 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派.静态分派的典型应用是方法重载.静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的. 那么什么是静态类型(static type)呢? 1 Super object = new Sub(); 像上面的语句,S

Jvm(65),方法调用----静态分派调用

我们先来看一下下面的例子: package demo.jvm.test8? public class Demo2 { /** 方法静态分派演示 * @author zzm */ static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public void sayHello(Human guy) { System.out.println("hel

Jvm(64),方法调用----解析

继续前面关于方法调用的话题,所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的.换句话说,调用目标在程序代码写好.编译器进行编译时就必须确定下来.这类方法的调用称为解析(Resolution). 换句话就是说在写好代码之后通过eclipse编译之后,编译出来的结果是不会再变化了. 在Java语言中符合"

多态方法调用的解析和分派

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍.最频繁的操作,Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用).这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法

JVM学习笔记(二)--方法调用之静态分配和动态分配

本篇文章从JVM的角度来理解Java学习中经常提到的重载和重写. 方法调用:方法调用不等同于方法执行,在Java虚拟机中,方法调用仅仅是为了确定调用哪个版本的方法.方法调用分为解析调用和分派.解析调用一定是静态的,而分派可以是静态的,也可以是动态的.我们这里只介绍分派中的静态分配和动态分配. 静态分配:所有依赖静态类型来定位方法执行版本的分派动作称为静态分配. 下面看个例子,顺便来猜一下结果(面试中经常遇到): 1 class Human { 2 3 } 4 5 class Man extend

JVM理论:(三/4)方法调用

本文主要总结虚拟机调用方法的过程是怎样的,JAVA虚拟机里面提供了5条方法调用的字节码指令.分别如下: invokestatic:调用静态方法 invokespecial:调用实例构造器<init>方法.私有方法和父类方法. invokevirtual:调用所有的虚方法. invokeinterface:调用接口方法,会在运行时期再确定一个实现此接口的对象. invokedynamic:现在运行时期动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条指令,分派逻辑都是固化在虚拟

JVM(十二):方法调用

JVM(十二):方法调用 在 JVM(七):JVM内存结构 中,我们说到了方法执行在何种内存结构上执行:Java 方法活动在虚拟机栈中的栈帧上,栈帧的具体结构在内存结构中已经详细讲解过了,下面就让我们来看一下 方法是如何调用的. 方法调用 首先,我们要明白一个基础性概念:方法调用并不是方法执行.其只是确定该调用哪一个方法而已(多态的影响,选择方法的不同版本).并且因为 Java 调用的动态性,有些方法需要在类加载阶段动态解析,这也为 JVM 解析符号引用成直接引用提供了难度. 解析 在 JVM(

JVM方法调用

当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才能确定是哪个方法,这也正是多态的实现原理. 对于第一种方法的调用,有2个字节码指令:invokestatic,invokespecial invokestatic:调用static方法(不需要通过类的实例就可以调用),这很好理解.静态方法属于整个类型,就一份,没有歧义. invokespecial: