klassVtable与klassItable类用来实现Java方法的多态,也可以叫动态绑定,是指在应用执行期间通过判断接受对象的实际类型,根据实际类型调用对应的方法。C++为了实现多态,在对象中嵌入了虚函数表vtable,通过虚函数表来实现运行期的方法分派,这在之前介绍HotSpot的二分模型时简单介绍过,这里不再介绍C++的方法分派。

1、klassVtable类

C++中的vtable只包含虚函数,非虚函数在编译期就已经解析出正确的方法调用了。Java vtable除了虚方法外还包含了其他的非虚方法。

vtable中的一条记录用vtableEntry表示,该类在klassVtable.hpp文件中定义,只定义了一个属性Method* _method,所以说此类只是对Method*做了简单包装而已,提供了操作Method对象的方法。

访问vtable需要通过klassVtable类,该类在klassVtable.hpp文件中定义,提供了操作vtable的方法,如Method* method_at(int i)、int index_of(Method* m)等,其实现都是基于vtable的内存起始地址和内存偏移完成的。

klassVtable类的定义及属性的声明如下:

A klassVtable abstracts the variable-length(可变长度) vtable that is embedded in InstanceKlass and ArrayKlass.
klassVtable objects are used just as convenient transient accessors to the vtable,not to actually hold the vtable data.
Note: the klassVtable should not be accessed before the class has been verified(until that point, the vtable is uninitialized). Currently a klassVtable contains a direct reference to the vtable data, and is therefore not preserved across GCs.
class klassVtable : public ResourceObj {
KlassHandle _klass; // my klass
int _tableOffset; // offset of start of vtable data within klass
int _length; // length of vtable (number of entries) ...
}

属性的介绍如下:

(1)_klass:该vtable所属的klass

(2)_tableOffset:vtable在klass实例内存中的偏移量

(3)_length:vtable的长度,即vtableEntry的数量,因为一个vtableEntry实例只包含一个Method*,其大小等于字宽(一个指针的宽度),所以vtable的长度跟vtable以字宽为单位的内存大小相同

下面介绍一下vtableEntry类。

vtableEntry类的定义及属性的声明如下:

class vtableEntry VALUE_OBJ_CLASS_SPEC {
...
private:
Method* _method;
...
};

这个类只是对_method进行了简单的封装。 

vtable表示是由一组变长(前面会有一个字段描述该表的长度)连续的vtableEntry元素构成的数组。其中每个vtableEntry封装了一个Method对象。在类初始化时,HotSpot将复制父类的vtable,然后根据自己定义的方法更新vtableEntry,或向vtable中添加新的vtableEntry对象。当Java方法重写父类方法时,HotSpot将更新vtable中表示被重写方法的vtableEntry,使其指向覆盖后的实现方法;如果是方法重载或者自身新增的方法,HotSpot将按顺序添加到vtable中。尚未提供实现的Java方法也放在了vtable中,因为没有实现,HotSpot没有为这个vtableEntry项分发具体的方法,这和C++的纯虚函数类似,不再赘述。调用类方法时,HotSpot通过ConstantPoolCacheEntry的_f2成员获取vtable中方法的索引,从而取到Method对象以便执行。关于ConstantPoolCacheEntry类及相关属性在后面会详细介绍。

2、klassItable

Java itable是Java接口函数表,为了方便查找某个接口对应的方法实现。itable的结构比vtable复杂,除了记录方法地址外还得记录该方法所属的接口类klass。

itable表由偏移表和方法表两个表组成,这两个表都是变长的。每个offset table entry保存的是类实现的一个接口klassOop和该接口方法表所在的偏移位置;方法表method table entry元素保存的是实现的接口方法,方法在方法表的位置同样是使用ConstantPoolCacheEntry的_f2成员保存的。在初始化itable时,HotSpot将类实现的接口以及实现的方法填写在上述两张表中。接口中的非public方法和abstract方法(在vtable中占一个槽位)不放入itable中。调用接口方法时,HotSpot通过ConstantPoolCacheEntry的_f1成员拿到接口的klassOop,在itable的偏移表中逐一匹配,如果匹配上则获取它的方法表的位置,然后在方法表中通过ConstantPoolCacheEntry的_f2成员找到实现的方法Method。
类及属性的定义如下:

class klassItable : public ResourceObj {
private:
instanceKlassHandle _klass; // my klass
int _table_offset; // offset of start of itable data within klass (in words)
int _size_offset_table; // size of offset table (in itableOffset entries)
int _size_method_table; // size of methodtable (in itableMethodEntry entries)
...
}

该类包含4个属性:

(1)_klass:itable所属的Klass

(2)_table_offset:itable在所属Klass中的内存偏移量

(3)_size_offset_table:itable中itableOffsetEntry的数量

(4)_size_method_table:itable中itableMethodEntry的数量

方法所属的接口类klass地址用itableOffsetEntry表示,类的定义如下:

class itableOffsetEntry VALUE_OBJ_CLASS_SPEC {
private:
Klass* _interface;
int _offset;
...
}

包含两个属性,如下:

(1) _interface:该方法所属的接口

(2)_offset:该接口下的第一个方法itableMethodEntry相对于所属Klass的偏移量

方法地址用itableMethodEntry表示,定义如下:

class itableMethodEntry VALUE_OBJ_CLASS_SPEC {
private:
Method* _method;
...
}

跟vtableEntry一样,只包含了一个_method属性。

为什么需要itable,而不是用vtable解决所有问题。

一个类可以实现多个接口,而每个接口的函数编号是和自己相关的,vtable 无法解决多个对应接口的函数编号问题。而一个子类只能继承一个父亲,子类只要包含父类vtable,并且和父类的函数包含部分编号是一致的,就可以直接使用父类的函数编号找到对应的子类实现函数。

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)

7、HotSpot的类模型(3)

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)

10、HotSpot的对象模型(6)

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器

14、类的双亲委派机制

15、核心类的预装载

16、Java主类的装载

17、触发类的装载

18、类文件介绍

19、文件流

20、解析Class文件

21、常量池解析(1)

22、常量池解析(2)

23、字段解析(1)

24、字段解析之伪共享(2)

25、字段解析(3)

26、字段解析之OopMapBlock(4)

27、方法解析之Method与ConstMethod介绍

28、方法解析

作者持续维护的个人博客classloading.com

关注公众号,有HotSpot源码剖析系列文章!

 

参考文章:

(1)C++与Java的多态性实现分析

(2)JVM Anatomy Park #16: 超多态虚调用  https://www.jianshu.com/p/704fce44840f

(3)The Black Magic of (Java) Method Dispatch  https://shipilev.net/blog/2015/black-magic-method-dispatch/

(4)https://www.zhihu.com/question/34846173?sort=created

(5)https://www.zhihu.com/question/56936880/answer/152203730

(6)https://hllvm-group.iteye.com/group/topic/29140

klassVtable与klassItable的更多相关文章

  1. 计算vtable的大小

    在ClassFileParser::parseClassFile()函数中会计算vtable和itable所需要的大小,因为vtable和itable是内嵌在Klass中的,parseClassFil ...

  2. 计算itable的大小

    在ClassFileParser::parseClassFile()函数中计算vtable和itable所需要的大小,之前已经介绍过vtable大小的计算,这一篇将详细介绍itable大小的计算过程. ...

  3. 解析Class文件之创建InstanceKlass对象

    ClassFileParser::parseClassFile()方法会将解析Class文件的大部分结果保存到instanceKlass对象中.创建instanceKlass对象的代码如下: int ...

  4. 初始化vtable

    在InstanceKlass::link_class_impl()方法中完成方法连接后会继续初始化vtable与itable,之前已经介绍过vtable与itable,并且在类解析过程中已经完成了大小 ...

  5. 初始化itable

    在InstanceKlass::link_class_impl()方法中完成方法连接后会继续初始化vtable与itable,之前已经介绍过vtable与itable,并且在类解析过程中已经完成了大小 ...

  6. HotSpot的执行引擎-CallStub栈帧

    之前多次提到接触到调用JavaCalls::call()方法来执行Java方法,如: (1)Java主类装载时,调用JavaCalls::call()方法执行的Java方法checkAndLoadMa ...

  7. Java引用类型之软引用(1)

    Java使用SoftReference来表示软引用,软引用是用来描述一些“还有用但是非必须”的对象.对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对 ...

  8. Java引用类型之软引用(2)

    下面接着上一篇介绍第2阶段和第3阶段的处理逻辑. 2.process_phase2() 第2个阶段移除所有的referent还存活的Reference,也就是从refs_list中移除Referenc ...

  9. Java引用类型之弱引用与幻像引用

    这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...

随机推荐

  1. Python面向对象02/类的空间问题、类与对象之间的关系、类与类之间的关系

    Python面向对象02/类的空间问题.类与对象之间的关系.类与类之间的关系 目录 Python面向对象02/类的空间问题.类与对象之间的关系.类与类之间的关系 1. 类的空间问题 2. 类与对象之间 ...

  2. Static关键字的使用

    Static关键字的使用 static关键字的作用: 用来修饰成员变量和方法,被修饰的成员是属于类的,而不单单是属于某个对象的,也就是说,可以不靠对象来调用. 首先我们来介绍类变量 当static修饰 ...

  3. 基于python的自动化测试简介【十年从业大佬】

    一.自动化测试包括以下几个方面: 1. 常用测试工具: (1)QTP:主要用于回归测试和测试同一软件的新版本 (2)Robot Framwork:python编写的功能自动化测试框架,具有良好的可扩展 ...

  4. SpringBoot 接收前端参数的几种方式

    昨天和前端小伙伴在联调是碰到了参数接收不到的错误,我在postman上测试接口是正常的,但是与前端对接时就接受不到参数,请求方式都是get,但是问题就在于json  和 form-data 的区别!这 ...

  5. Makefile中的一个坑

    问题描述:Makefile中,我想将一个变量的后缀全部进行替换,如将所有的.c后缀变成.d后缀 方法:$(CUR_SOURCE: .c = .d ) 说明:查阅相关资料,了解到上述这种语法就可以将所有 ...

  6. 深入掌握K8S Pod

    k8s系列文章: 什么是K8S K8S configmap介绍 Pod是k8s中最小的调度单元,包含了一个"根容器"和其它用户业务容器. 如果你使用过k8s的话,当然会了解pod的 ...

  7. canvas学习01

    canvas 必须指定宽高,确定可绘图区域的大小 canvas标签里写的是浏览器不支持canvas时展示的内容 <canvas id="drawing" width=&quo ...

  8. P3756 [CQOI2017]老C的方块

    题目链接 看到网格图+最优化问题,当然要想黑白染色搞网络流.不过这道题显然无法用黑白染色搞定. 仔细观察那四种图形,发现都是蓝线两边一定有两个格子,两个格子旁边一定还有且仅有一个格子.因此我们可以这么 ...

  9. wpf文字模糊

    wpf如果使用了DropShadowEffect,会导致文字模糊,可以在window上设置 this.UseLayoutRounding = true;解决此问题

  10. onsubmit校验表单时利用ajax的return false无效解决方法-转

    原来的代码 function checkNewEmail(){ var re_email=new RegExp("\\w+@\\w+\\.\\w+\\.?\\w*");      ...