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中有关方法调用的知识. 目的 方法调用,目的是选择方 ...
随机推荐
- Burpsuite暴力破解
神器:burpsuite 闲话不多说,直接开搞 1.打开文件BurpLoader.jar,进入Proxy--Options,启用代理 2.打开浏览器(IE),进入Internet选项-连接-局域网设置 ...
- android 关于表格布局的认识
表格布局(TableLayout) 使用的知识点有: 控件 TableRow:为这个表格添加一行 table的特殊属性 android:layout_column:确定此表格的列数 android:s ...
- WP8.1小梦词典开发1:金山词霸API使用
原文出自:http://www.bcmeng.com/windows-phone-api/ 今天开始小梦给大家分享一下小梦词典开发中几个关键问题,首先我们来看查词功能的实现.小梦词典的查词功能是通过金 ...
- Ubuntu14.04下搜狗输入法的安装及配置
在搜狗官网上下载相应的版本32/64 搜狗网址:http://pinyin.sogou.com/linux/?r=pinyin 在文件夹中找到下载的搜狗输入法文件(默认位置是Downloads),双击 ...
- 配置uwsgi
首先要明确的是,如果你喜欢用命令行的方式(如shell)敲命令,那可以省去任何配置. 但是,绝大多数人,还是不愿意记那么长的命令,反复敲的.所以uwsgi里,就给大家提供了多种配置,省去你启动时候,需 ...
- es6 箭头函数(arrow function) 学习笔记
箭头函数有两个好处. 1.他们比传统函数表达式简洁. const arr = [1, 2, 3]; const squares = arr.map(x => x * x); // 传统函数表达式 ...
- java实现8种排序算法(详细)
八种排序分别是:直接插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 希尔排序在时间性能上优于直接插入排序,但希尔排序是一种不稳定排序. 快速排序的时间性能也优于冒泡 ...
- 使用Python对Excel进行读写操作
学习Python的过程中,我们会遇到Excel的读写问题.这时,我们可以使用xlwt模块将数据写入Excel表格中,使用xlrd模块从Excel中读取数据.下面我们介绍如何实现使用Python对Exc ...
- 想系统的学习一下项目管理,推荐PRINCE2
参加pmp培训,考个pmp证书就足矣应付面试. 个人并不推荐看那些外文原版书籍,我看过一两本,水平与实用性并不是你想象的那么好,除非你是做理论研究,为了发表论文. 另一本就推荐prince2的教材,p ...
- 【C++】浅谈三大特性之一继承(一)
一,为什么要引入继承? 继承是一个非常自然的概念,现实世界中的许多事物也都是具有继承性的. 例如,爸爸继承爷爷的特性,儿子又继承爸爸的特性等都属于继承的范畴.下面是一个简单的汽车分类图: 在这个分类图 ...