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)方法。
该方法的字节码指令证明了上述的论证。

参考资料:
1. 周志明 《深入理解Java虚拟机》
2. 爪哇人 【解惑】Java动态绑定机制的内幕
JVM 方法调用之动态分派的更多相关文章
- JVM 方法调用之静态分派
分派(Dispatch)可能是静态也可能是动态的,根据分派依据的宗量数可分为单分派和多分派.这两种分派方式的两两组合就构成了静态单分派,静态多分派,动态单分派,动态多分派这4种组合.本章讲静态分派. ...
- java方法调用之动态调用多态(重写override)的实现原理——方法表(三)
上两篇篇博文讨论了java的重载(overload)与重写(override).静态分派与动态分派.这篇博文讨论下动态分派的实现方法,即多态override的实现原理. java方法调用之重载.重写的 ...
- JVM方法调用过程
JVM方法调用过程 重载和重写 同一个类中,如果出现多个名称相同,并且参数类型相同的方法,将无法通过编译.因此,想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同.这种方法上的联系就是重载 ...
- JVM 方法调用之解析
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还没有涉及到方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍最频繁的操作,但Class文件 ...
- JVM方法调用
当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才 ...
- Spring杂谈 | 从桥接方法到JVM方法调用
前言 之所以写这么一篇文章是因为在Spring中,经常会出现下面这种代码 // 判断是否是桥接方法,如果是的话就返回这个方法 BridgeMethodResolver.findBridgedMetho ...
- JVM方法调用栈
摘自深入分析java web技术内幕
- JAVA方法调用中的解析与分派
JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...
- JVM系列-方法调用的原理
JVM系列-方法调用的原理 最近重新看了一些JVM方面的笔记和资料,收获颇丰,尤其解决了长久以来心中关于JVM方法管理的一些疑问.下面介绍一下JVM中有关方法调用的知识. 目的 方法调用,目的是选择方 ...
随机推荐
- iOS多线程——GCD
最近的项目遇到了很多多线程的问题,借此机会对GCD进行了一番学习并总结.首先说一下什么是GCD,GCD全称 Grand Central Dispatch,是异步执行任务的技术之一.开发者只需要定义想要 ...
- Chapter 4. Working with Key/Value Pairs
Chapter4 working with key/value pairs key/values pairs键值对是Spark中非常常见的一种数据类型(type),RDD有时经常操作键值对数据类型.第 ...
- python服务器环境搭建(2)——安装相关软件
在上一篇我们在本地的虚拟服务器上安装好CentOS7后,我们的python web服务.自定义的python service或python脚本需要在服务器上运行,还需要在服务器安装各种相关的软件才行, ...
- C++枚举类型详解
原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5171110.html 一.枚举类型的定义 enum 类型名 {枚举值表}: 类型名是变量名,指定 ...
- Docker(开课吧笔记)
1.Docker基本概念 Docker运行在Linux,需要git技能 docker官网解析 来源于容器又不仅仅是容器,第一个版本基于LXC,远远超过容器概念 交付时拿到的是镜像,直接run运 ...
- iOS开发之判断横竖屏切换
/** * 当屏幕即将旋转的时候调用 * * @param toInterfaceOrientation 旋转完毕后的最终方向 * @param duration 旋转动画所花费的时间 */ ...
- Jmeter-线程组
1.Sampler 取样器(Sampler)是性能测试中向服务器发送请求,记录响应信息,记录响应时间的最小单元,JMeter 原生支持多种不同的sampler , 如 HTTP Request Sam ...
- 腾讯云数据库团队:PostgreSQL TOAST技术理解
作者介绍:胡彬 腾讯云高级工程师 TOAST是"The Oversized-Attribute Storage Technique"的缩写,主要用于存储一个大字段的值.要理解TOA ...
- centos 6.5 搭建JSP运行环境
一.安装nginx yum install nginx #安装nginx,根据提示,输入Y安装即可成功安装 service nginx start #启动 chkconfig nginx on #设为 ...
- MongoDB基础教程系列--第三篇 MongoDB基本操作(二)
1.集合操作 1.1.创建集合 MongoDB 用 db.createCollection(name, options) 方法创建集合. 格式 db.createCollection(name, op ...