JVM 方法调用之动态分派(详解)

1. 动态分派

一个体现是重写(override)。下面的代码,运行结果很明显。

public class App {

 public static void main(String[] args) {
  Super object = new Sub();
  object.f();
 }
}

 class Super {
 public void f() {
  System.out.println("super : f()");
 }

 public void f(int i) {
  System.out.println("super : f(int)");
 }
}

class Sub extends Super{

 @Override
 public void f() {
  System.out.println("sub : f()");
 }

 @Override
 public void f(int i) {
  System.out.println("sub : f(int)");
 }

 public void f(char c) {
  System.out.println("sub : f(char)");
 }
}

最终输出sub : f();

那么虚拟机是怎么做到动态分派的呢?

不同的虚拟机有不同的实现,最常用的是使用虚方法表(Virtual Method Table)

2. 虚方法表

对于Super和Sub类,虚方法表大致如下:(灵魂画师)

上面的灵魂画作是什么意思呢?

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

从上图主要得出几个信息:

a. 上图的大部分方法,子类Super和Sub均没有重写,那么都指向父类Object的类型数据。f()和f(int)方法,父类子类都实现了,那么两者就指向不同的实现地址。f(char)只在子类定义实现,自然指向子类的类型数据。

b. 为了程序实现上的方便,具有相同签名的方法,在父类,子类的虚方法表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需要的入口地址。

3. 实例分析

以本文开头的代码进行分析。通过javap命令查看main方法的指令。

其中的invokevirtual指令详细调用过程是这样的:

1)指令中的#19指的是App类的常量池中第19个常量表的索引项。这个常量表(CONSTATN_Methodref_info)记录的是方法f()信息的符号引用,JVM首先根据这个符号引用找到调用方法f()的类的全限定名com.khlin.Super,这是因为变量object被声明为Super类型。

2) 在Super类型的方法表中查找方法f(),如果找到,则将方法f()在方法表中的索引项(具体值我不了解,这里将其记为index) 记录到App类的常量池中第19个常量表中(常量池解析)。因此,如果Super类型方法表中没有f(),那么即使Sub类型的方法表有该方法,也会报编译失败。

3)在调用invokevirtual指令前有一个aload_1指令,它会将开始创建中堆中的Sub对象的引用压入操作数栈。然后invokevirtual指令会根据这个Sub对象的引用首先找到堆中的Sub对象,然后进一步找到Sub对象所属类型的方法表。

4)这时,通过2)查找的index,可以定位到Sub类型方法表中的f()方法,然后通过直接地址找到该方法字节码所在的内存空间。这就是父类和子类相同签名的方法索引序号一致的用处。

4. 综合考虑:一个可能想错的例子

将本文开头的代码里的main方法稍作修改,调用其他的方法。

public static void main(String[] args) {
   Super object = new Sub();
   char c = 'a';
   object.f(c);
  }

结果将输出sub : f(int)

明明Sub方法里有完全一样类型的f(char)方法,却调用的是f(int).

相信通过前面的学习,已经可以明白原因了。

在object.f(c)调用时,虚拟机先到Super类的方法表里,查找最为合适的方法。

Super类里没有刚好参数为char的f(char)方法,按照前面静态分派和参数类型自动转换的学习,可以知道,编译器使用了除了f(char)之外最为合适的方法f(int)。获取到索引后,通过索引到实际对象的Sub方法表里找到f(int)方法,最终执行的就是Sub类的f(int)方法。

该方法的字节码指令证明了上述的论证。

以上这篇JVM 方法调用之动态分派(详解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JVM 方法调用之动态分派(详解)

    1. 动态分派 一个体现是重写(override).下面的代码,运行结果很明显. public class App { public static void main(String[] args) { Super object = new Sub(); object.f(); } } class Super { public void f() { System.out.println("super : f()"); } public void f(int i) { System.out

  • JVM 方法调用之静态分派(详解)

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

  • StackTraceElement获取方法调用栈信息实例详解

    本文研究的主要是StackTraceElement获取方法调用栈信息的相关内容,具体介绍和实例如下. 一.什么是StackTrace StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,异常处理中常用的printStackTrace()实质就是打印异常调用的堆栈信息. 二.StackTraceElement介绍 StackTraceElement表示StackTrace(堆栈轨迹)中的一个方法对象,属性包括方法的类名.方法名.文件名以及调用的行数. public final class

  • JVM方法调用invokevirtual详解

    在java代码运行期间,方法间的调用可以说是最为频繁的了,那么这些方法间的调用在底层的虚拟机又做了什么事情呢?现在就让我们揭开那道神秘的面纱. JVM调用方法有五条指令,分别是invokestatic,invokespecial,invokevirtual,invokeinterface,invokedynamic.invokestatic用来调用静态方法:invokespecial用来调用私有方法,父类方法(super.),类构造器方法:invokeinterface调用接口方法:invoke

  • Java方法调用解析静态分派动态分派执行过程

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

  • java 方法与数组基础使用详解

    目录 一.方法的使用 1.方法的定义 2.方法重载 二.数组的定义和使用 1.数组的基本概念 (1)数组的创建 (2)数组的初始化 (3)数组的遍历 2.数组是引用类型(JVM的内存分布) 3.引用变量 4.数组拷贝函数 5.二维数组的for.each遍历 一.方法的使用 1.方法的定义 java中的方法就相当于C语言中的函数 方法的语法格式 //方法的定义 修饰符  返回值类型  方法的名称([参数类型 参数]){ 方法体代码: [return 返回值]: } [注意事项] 修饰符:现阶段直接

  • Java工厂模式用法之如何动态选择对象详解

    目录 前言 小菜鸟的问题 有没有更好的方法呢 还有什么更好的办法吗 还能做得更好吗 如何在 SpringBoot 中实现此技术 总结 前言 工厂设计模式可能是最常用的设计模式之一,我想大家在自己的项目中都用到过.可能你会不屑一顾,但这篇文章不仅仅是关于工厂模式的基本知识,更是讨论如何在运行时动态选择不同的方法进行执行,你们可以看看是不是和你们项目中用的一样? 小菜鸟的问题 直接上例子说明,设计一个日志记录的功能,但是支持记录到不同的地方,例如: 内存中 磁盘上的文件 数据库 百度网盘等远程存储服

  • python类的方法属性与方法属性的动态绑定代码详解

    动态语言与静态语言有很多不同,最大的特性之一就是可以实现动态的对类和实例进行修改,在Python中,我们创建了一个类后可以对实例和类绑定心的方法或者属性,实现动态绑定. 最近在学习python,纯粹是自己的兴趣爱好,然而并没有系统地看python编程书籍,觉得上面描述过于繁琐,在网站找了一些学习的网站,发现廖雪峰老师的网站上面的学习资源很不错,而且言简意赅,提取了一些python中的重要的语法和案例.重要的是可以在线测试python的运行代码,缺点就是没有系统的看python的书籍,不能及时的将

  • 超全MyBatis动态代理详解(绝对干货)

    前言 假如有人问你这么几个问题,看能不能答上来 Mybatis Mapper 接口没有实现类,怎么实现的动态代理 JDK 动态代理为什么不能对类进行代理(充话费送的问题) 抽象类可不可以进行 JDK 动态代理(附加问题) 答不上来的铁汁,证明 Proxy.Mybatis 源码还没看到位.不过没有关系,继续往下看就明白了 动态代理实战 众所周知哈,Mybatis 底层封装使用的 JDK 动态代理.说 Mybatis 动态代理之前,先来看一下平常我们写的动态代理 Demo,抛砖引玉 一般来说定义 J

  • JVM类加载器之ClassLoader的使用详解

    目录 类加载器 概述 加载器的种类 验证不同加载器 核心方法 JVM类加载机制的三种方式 全盘负责 父类委托.双亲委派 缓存机制 打破双亲委派 重写loadclass方法 自定义类加载器 准备字节码文件 创建自定义类加载器 执行测试 注意事项 类加载器 概述 类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块. 类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性. 任意一个类,都由加载它的类加载器和这个类本身一同确定其在 Java 虚拟

随机推荐