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 方法调用之动态分派的更多相关文章

  1. JVM 方法调用之静态分派

    分派(Dispatch)可能是静态也可能是动态的,根据分派依据的宗量数可分为单分派和多分派.这两种分派方式的两两组合就构成了静态单分派,静态多分派,动态单分派,动态多分派这4种组合.本章讲静态分派. ...

  2. java方法调用之动态调用多态(重写override)的实现原理——方法表(三)

    上两篇篇博文讨论了java的重载(overload)与重写(override).静态分派与动态分派.这篇博文讨论下动态分派的实现方法,即多态override的实现原理. java方法调用之重载.重写的 ...

  3. JVM方法调用过程

    JVM方法调用过程 重载和重写 同一个类中,如果出现多个名称相同,并且参数类型相同的方法,将无法通过编译.因此,想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同.这种方法上的联系就是重载 ...

  4. JVM 方法调用之解析

    方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还没有涉及到方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍最频繁的操作,但Class文件 ...

  5. JVM方法调用

    当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才 ...

  6. Spring杂谈 | 从桥接方法到JVM方法调用

    前言 之所以写这么一篇文章是因为在Spring中,经常会出现下面这种代码 // 判断是否是桥接方法,如果是的话就返回这个方法 BridgeMethodResolver.findBridgedMetho ...

  7. JVM方法调用栈

    摘自深入分析java web技术内幕

  8. JAVA方法调用中的解析与分派

    JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...

  9. JVM系列-方法调用的原理

    JVM系列-方法调用的原理 最近重新看了一些JVM方面的笔记和资料,收获颇丰,尤其解决了长久以来心中关于JVM方法管理的一些疑问.下面介绍一下JVM中有关方法调用的知识. 目的 方法调用,目的是选择方 ...

随机推荐

  1. Redis的发布订阅及.NET客户端实现

    序言 发布订阅在设计模式中也可以说是观察者模式,针对这个模式是处理对象间一对多的依赖关系的,当一个对象发生变化,其它依赖他的对象都要得到通知并更新. 然而它也有自己的缺点,就是当主题发生一系列的变化时 ...

  2. 【转】SQL Server海量数据库的索引、查询优化及分页算法

    探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页.以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构: CREATE TABLE [dbo]. ...

  3. iOS开发之UIDynamic

    1.概述 什么是UIDynamic? UIDynamic是从iOS 7开始引入的一种新技术,隶属于UIKit框架. 可以认为是一种物理引擎,能模拟和仿真现实生活中的物理现象.比如:重力.弹性碰撞等现象 ...

  4. css——样式表分类,选择器

    一,样式表分类 (1)内联样式[优先级最高][常用][代码重复使用性最差] (当特殊的样式需要应用到个别元素时,就可以使用内联样式. 使用内联样式的方法是在相关的标签中使用样式属性.样式属性可以包含任 ...

  5. vue学习笔记 计算属性(四)

    计算属性就是vue实例里的computed属性,对应一个对象,里面可以放各种方法,方法的作用就是可以生成和数据变量对应的计算后的变量,跟数据相关的复杂逻辑变量,都可以使用计算属性实现,computed ...

  6. python应用部署--flask

    首先必须吐槽一下,python应用部署简直就是有毒...太麻烦了.关键还不能成功部署. 网上很多教程都是说要用nginx和uwsgi.来来回回试了无数次都不行.于是乎,在某一个瞬间,灵感以来,发现了一 ...

  7. React中使用CSSTransitionGroup插件实现轮播图

    动画效果,是一个页面上必不可少的功能,学习一个新的东西,当然就要学习,如何用新的东西,用它的方法去实现以前的东西啦.今天呢,我就在这里介绍一个试用react-addons-css-transition ...

  8. flex盒子里面元素linehight对高度的影响

    那天,高高兴兴的测试flex属性.大家都知道,当父盒子display:flex的时候,子盒子即使宽高和大于父盒子,也会由于flex的特性进行收缩:然而,在给父盒子加了flex-direction:co ...

  9. Omi新成员omi-router正式发布

    原文链接-https://github.com/AlloyTeam/omi/blob/master/tutorial omi-router omi-router是Omi框架专属的router插件,文件 ...

  10. Servlet3.0新特性使用详解

    可插拔的Web框架 几乎所有基于Java的web框架都建立在servlet之上.现今大多数web框架要么通过servlet.要么通过Web.xml插入.利用标注(Annotation)来定义servl ...