Java方法调用的虚分派

JUN 2ND, 2013 | COMMENTS

本文通过介绍 Java 方法调用的虚分派,来加深对 Java 多态实现的理解。需要预先理解 Java 字节码和 JVM 的基本框架。

虚分配(Virtual Dispatch)

首先从字节码中对方法的调用说起。Java 的 bytecode 中方法的调用实现分为四种指令:

  • 1.invokevirtual 为最常见的情况,包含 virtual dispatch 机制;
  • 2.invokespecial 是作为对 private 和构造方法的调用,绕过了 virtual dispatch;
  • 3.invokeinterface 的实现跟 invokevirtual 类似。
  • 4.invokestatic 是对静态方法的调用。

其中最复杂的要属 invokevirtual 指令,它涉及到了多态的特性,使用 virtual dispatch 做方法调用。

virtual dispatch 机制会首先从 receiver(被调用方法的对象)的类的实现中查找对应的方法,如果没找到,则去父类查找,直到找到函数并实现调用,而不是依赖于引用的类型。

下面是一段有趣的代码。反映了 virtual dispatch 机制 和 一般的 field 访问的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Greeting {
String intro = "Hello";
String target(){
return "world";
}
} public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
} public static void main(String[] args){
Greeting english = new Greeting();
Greeting french = new FrenchGreeting(); System.out.println(english.intro + "," + english.target());
System.out.println(french.intro + "," + french.target());
System.out.println(((FrenchGreeting)french).intro + "," + ((FrenchGreeting)french).target());
}
}

运行的结果为

1
2
3
Hello,world
Hello,le monde
Bonjour,le monde

前两行输出中,对于 intro 这个属性的访问,直接指向了父类中的变量,因为引用类型为父类。

第二行对于 target()的方法调用,则是指向了子类中的方法,虽然引用类型也为父类,但这是虚分派的结果,虚分派不管引用类型的,只查被调用对象的类型。

既然虚分派机制是从被调用对象本身的类开始查找,那么对于一个覆盖了父类中某方法的子类的对象,是无法调用父类中那个被覆盖的方法的吗?

在虚分派机制中这确实是不可以的。但却可以通过 invokespecial 实现。如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
} public String func(){
return super.target();
} public static void main(String[] args){
Greeting english = new Greeting();
FrenchGreeting french = new FrenchGreeting(); System.out.println(french.func()); }
}

func()就成功的调用了父类的方法 target(),虽然 target()已经被子类重写了。具体的调用细节,从字节码中可以看到:

1
2
3
ALOAD 0: this
INVOKESPECIAL Greeting.target() : String
ARETURN

其中使用了 invokespecial 指令,而不是施行虚分派策略的 invokevirtual 指令。

方法表(Method Table)

介绍了虚分派,接下来介绍是它的一种实现方式 – 方法表。类似于 C++的虚函数表 vtbl。

在有的 JVM 实现中,使用了方法表机制实现虚分派,而有时候,为了节省内存可能不采用方法表的实现。

不要被方法表这个名字迷惑,它并不是记录所有方法的表。它是为虚分派服务,不会记录用 invokestatic 调用的静态方法和用 invokespecial 调用的构造函数和私有方法。

JVM 会在链接类的过程中,给类分配相应的方法表内存空间。每个类对应一个方法表。这些都是存在于 method area 区中的。这里与 C++略有不同,C++中每个对象的第一个指针就是指向了相应的虚函数表。而 Java 中每个对象索引到对应的类,在对应的类数据中对应一个方法表。(关于链接的更多信息,参见博文《Java
类的装载、链接和初始化》

一种方法表的实现如下:

父类的方法比子类的方法先得到解析,即父类的方法相比子类的方法位于表的前列。

表中每项对应于一个方法,索引到实际方法的实现代码上。如果子类重写了父类中某个方法的代码,则该方法第一次出现的位置的索引更换到子类的实现代码上,而不会在方法表中出现新的项。

JVM 运行时,当代码索引到一个方法时,是根据它在方法表中的偏移量来实现访问的。(第一次执行到调用指令时,会执行解析,将符号索引替换为对应的直接索引)。

由于 invokevirtual 调用的方法在对应的类的方法表中都有固定的位置,直接索引的值可以用偏移量来表示。(符号索引解析的最终目的是完成直接索引:对象方法和对象变量的调用都是用偏移量来表示直接索引的)

invokeinterface 与 invokevirtual 的比较

当使用 invokeinterface 来调用方法时,由于不同的类可以实现同一 interface,我们无法确定在某个类中的 inteface 中的方法处在哪个位置。于是,也就无法解析 CONSTANT_intefaceMethodref-info 为直接索引,而必须每次都执行一次在 methodtable 中的搜索了。 所以,在这种实现中,通过 invokeinterface 访问方法比通过 invokevirtual 访问明显慢很多。

参考资料

原文地址:http://biaobiaoqi.github.com/blog/2013/06/02/virtual-dispatch-and-method-table-in-java/

 版权声明:自由转载-非商用-非衍生-保持署名| Creative Commons BY-NC-ND 3.0

方法的虚分派(virtual dispatch)和方法表(method table)的更多相关文章

  1. java方法的虚分派和方法表

    java:方法的虚分派(virtual dispatch)和方法表(method table) Java方法调用的虚分派 虚分配(Virtual Dispatch) 首先从字节码中对方法的调用说起.J ...

  2. C++虚方法(虚函数)随笔

    本文不讨论虚函数的原理,只简单总结下虚函数的常用事项. 虚函数(虚方法)是C++动态联编 实现多态的重要手段,在函数声明时使用关键字virtual即可,如: virtual void func(voi ...

  3. .net学习之继承、里氏替换原则LSP、虚方法、多态、抽象类、Equals方法、接口、装箱拆箱、字符串

    1.继承(1)创建子类对象的时候,在子类对象中会为子类对象的字段开辟空间,也会为父类的所有字段开辟空间,只不过父类私有的成员访问不到(2)子类从父类继承父类所有的非私有成员,但是父类的所有字段也会创建 ...

  4. C#三大方法:虚方法、静态方法、实例方法

    虚方法:使用virtual关键字定义,当子类继承父类时,可以对父类中的虚方法进行重写. 如下面代码中的类B,它继承类A,类A实现了接口I(实现了接口中的foo()方法).在类A中使用virtual将f ...

  5. 通过字节码分析Java方法的静态分派与动态分派机制

    在上一次[https://www.cnblogs.com/webor2006/p/9723289.html]中已经对Java方法的静态分派在字节码中的表现了,也就是方法重载其实是一种静态分派的体现,这 ...

  6. C++ - 复制(copy) 和 虚复制(virtual copy) 的 区别

    复制(copy) 和 虚复制(virtual copy) 的 区别 本文地址: http://blog.csdn.net/caroline_wendy/article/details/16120397 ...

  7. C#虚函数virtual详解

    在面向对象编程中,有两种截然不同的继承方式:实现继承和接口继承.在实现继承时候,在Java中,所有函数默认都是virtual的,而在C#中所有函数并不默认为virtual的,但可以在基类中通过声明关键 ...

  8. C#中的虚函数virtual

    简单介绍虚函数virtual 在某基类中声明 virtual 并在一个或多个派生类中被重新定义的成员函数称为虚函数. 虚函数的作用就是实现多态性(Polymorphism),多态性是将接口与实现进行分 ...

  9. C++ 多态、虚函数(virtual 关键字)、静态联编、动态联编

    函数重写:(在子类中重写父类中的函数) 父类中被重写的函数  依然会继承  给子类. 子类中重写的函数将覆盖父类中的函数. 通过作用域分辨符  ::  可以访问到父类中的函数. 例如: #includ ...

  10. YbSoftwareFactory 代码生成插件【二十五】:Razor视图中以全局方式调用后台方法输出页面代码的三种方法

    上一篇介绍了 MVC中实现动态自定义路由 的实现,本篇将介绍Razor视图中以全局方式调用后台方法输出页面代码的三种方法. 框架最新的升级实现了一个页面部件功能,其实就是通过后台方法查询数据库内容,把 ...

随机推荐

  1. C primer plus笔记之初识C语言

    初识C语言 --本文参考书籍:         Stephen Prata的<C Primer Plus> 前言 C 语言是一门抽象的.面向过程的语言,C 语言广泛应用于底层开发,C 语言 ...

  2. CCIA数安委等组织发起“个人信息保护影响评估专题工作”,合合信息首批入选试点

    近期,"个人信息保护影响评估专题工作"(简称"PIA专题工作")试点评估结果正式发布.PIA专题工作组由中国电子技术标准化研究院.中国信息通信院等单位的法律与技 ...

  3. 【原创】解决NasCab掉进程,NasCab进程维护

    最近对象吐槽家里服务器又连不上,看不了考研视频了. 我掏出手机一试,确实连不上.家里的服务器是Win11平台,用NasCab管理的视频文件,然后通过frpc做的内网穿透. 我们在外面的图书馆,连不上无 ...

  4. MyBatis动态增改,多字段模糊查询

    示例: insert insert into bargain_products <trim prefix="(" suffix=")" suffixOve ...

  5. mono 下运行 VB.NET 编写的 WinForm 程序

    操作系统环境 UOS  20 安装 Mono 可以参考 dotnet 在 UOS 国产系统上安装 Mono 开发工具的方法 要点如下 nano /etc/apt/sources.list 增加一行 D ...

  6. 【问题解决】remote: parse error: Invalid numeric literal at line 1, column 20,解决思路

    问题现象 某同事出现过同样的推送到git仓库报错的问题,报错信息详情如下: Delta compresion using up to 20 threads Compressing objects: 1 ...

  7. Android Qcom USB Driver学习(二)

    BC v1.2充电规范 Battery Charging Specification USB port 如何识别不同的Charger类型 USB Charger类型 USB_SDP_CHARGER P ...

  8. 墨天轮国产数据库沙龙 | 四维纵横姚延栋 :MatrixDB,All-in-One高性能时序数据库

    分享嘉宾:姚延栋 北京四维纵横数据有限公司创始人.原Greenplum 北京研发中心总经理.Greenplum中国开源社区创始人.PostgreSQL中文社区常委.壹零贰肆数字基金会(非营利组织)联合 ...

  9. 封装JWT - 生成 jwt 和解析 jwt

    1. ASP.NET Core 身份验证和授权验证的功能由Authentication,Authorization中间件提供 :app.UseAuthentication(),app.UseAutho ...

  10. Vue的nextTick的原理

    知识储备:事件循环有宏任务和微任务,宏任务所处的队列就是宏任务队列,队列可以有多个,第一个队列只有一个任务就是执行主线程的js代码,剩余队列任务有setTimeout setInterval微任务所处 ...