HotSpot采用了OOP-Klass模型来描述Java类和对象。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象的具体类型。

那么为何要设计这样一个一分为二的对象模型呢?因为类和对象本来就不是一个概念,分别使用不同的对象模型描述符合软件开发的设计思想。另外英文注释也说明了其中的一个原因:

One reason for the oop/klass dichotomy in the implementation is that we don't want a C++ vtbl pointer in every object. Thus,
normal oops don't have any virtual functions. Instead, they forward all "virtual" functions to their klass, which does have
a vtbl and does the C++ dispatch depending on the object's actual type. (See oop.inline.hpp for some of the forwarding code.)

根据注释描述,HotSopt的设计者不想让每个对象中都含有一个vtable(虚函数表),所以就把对象模型拆成klass和oop,其中oop中不含有任何虚函数,而klass就含有虚函数表,可以进行方法分发。

我们简单介绍一下C++中对象的内存布局,这样才能了解二分模型设计的原因。同时也要介绍一下关于C++中虚函数的分派,这样在讲解Java语言的多态时就不用再补这一块的C++知识了。

下面分情况介绍C++对象的内存局部。

1、只含有数据成员的对象 

class Base1{

public:
int base1_var1;
int base1_var2; };

通过在VS中配置/d1 reportSingleClassLayoutBase1命令来查看对象的内存布局,如下:

1>  class Base1	size(8):
1> +---
1> 0 | base1_var1
1> 4 | base1_var2
1> +---

可以看到,成员变量是按照定义的顺序来保存的,类对象的大小就是所有成员变量的大小之和。 

2、没有虚函数的对象

class Base1{

public:
int base1_var1;
int base1_var2; void func(){}
};

C++中有方法的动态分派,就类似于Java中方法的多态。而C++实现动态分派主要就是通过虚函数来完成的,非虚函数在编译时就已经确定调用目标。C++中的虚函数通过关键字virtual来声明,如上函数func()没有virtual关键字,所以是非虚函数。  

查看内存布局,如下:

1>  class Base1	size(8):
1> +---
1> 0 | base1_var1
1> 4 | base1_var2
1> +---

非虚函数不会影响内存布局。 

3、含有虚函数的对象 

class Base1{

public:
int base1_var1;
int base1_var2; virtual void base1_fun1() {} };

内存布局如下:

1>  class Base1	size(16):
1> +---
1> 0 | {vfptr}
1> 8 | base1_var1
1> 12 | base1_var2
1> +---

在64位环境下,指针占用8字节,而vfptr就是指向虚函数表(vtable)的指针,其类型为void**, 这说明它是一个void*指针。类似于在类Base1中定义了如下类似的伪代码:

void* vtable[1] = {  &Base1::base1_fun1  };

const void**  vfptr = &vtable[0];

另外我们还可以看到,虚函数指针vfptr位于所有的成员变量之前。 

我们在上面的例子中再添加一个虚函数,如下:

virtual void base1_fun2() {}

内存布局如下:

1>  class Base1	size(16):
1> +---
1> 0 | {vfptr}
1> 8 | base1_var1
1> 12 | base1_var2
1> +---

可以看到,内存布局无论有一个还是多个虚函数都是一样的,改变的只是vfptr指向的虚函数表中的项。类似于在类Base1中定义了如下类似的伪代码: 

void* vtable[] = { &Base1::base1_fun1, &Base1::base1_fun2 };

const void** vfptr = &vtable[0];

  

4、继承类对象

class Base1{

public:

int base1_var1;
int base1_var2; virtual void base1_fun1() {}
virtual void base1_fun2() {} }; class Derive1 : public Base1{ public: int derive1_var1;
int derive1_var2; };

通过在VS中配置/d1 reportSingleClassLayoutDerive1命令来查看Derive1对象的内存布局,如下:

1>  class Derive1	size(24):
1> +---
1> | +--- (base class Base1)
1> 0 | | {vfptr}
1> 8 | | base1_var1
1> 12 | | base1_var2
1> | +---
1> 16 | derive1_var1
1> 20 | derive1_var2
1> +---

可以看到,基类在上边, 继承类的成员在下边,并且基类的内存布局与之前介绍的一模一样。继续来改造如上的实例,为派生类Derive1添加一个与基本base1_fun1()函数一模一样的虚函数,如下:

class Base1{

public:

int base1_var1;
int base1_var2; virtual void base1_fun1() {}
virtual void base1_fun2() {} }; class Derive1 : public Base1{ public: int derive1_var1;
int derive1_var2; virtual void base1_fun1() {} // 覆盖基类函数 };

布局如下:

1>  class Derive1	size(24):
1> +---
1> | +--- (base class Base1)
1> 0 | | {vfptr}
1> 8 | | base1_var1
1> 12 | | base1_var2
1> | +---
1> 16 | derive1_var1
1> 20 | derive1_var2
1> +---

基本的布局没变,不过由于发生了虚函数覆盖,所以虚函数表中的内容已经发生了变化,类似于在类Derive1中定义了如下类似的伪代码:  

void* vtable[] = { &Derive1::base1_fun1, &Base1::base1_fun2 };

const void** vfptr = &vtable[0];

可以看到,vtable[0]指针指向的是Derive1::base1_fun1()函数。所以当调用Derive1对象的base1_fun1()函数时,会根据虚函数表找到Derive1::base1_fun1()函数进行调用,而当调用Base1对象的base1_fun1()函数时,由于Base1对象的虚函数表中的vtable[0]指针指向Base1::base1_func1()函数,所以会调用Base1::base1_fun1()函数。是不是和Java中方法的多态很像?那么HotSpot虚拟机是怎么实现Java方法的多态呢?我们后续在讲解Java方法时会详细介绍。

下面继续看虚函数的相关实例,如下:

class Base1{

public:

int base1_var1;
int base1_var2; virtual void base1_fun1() {}
virtual void base1_fun2() {} }; class Derive1 : public Base1{ public: int derive1_var1;
int derive1_var2; virtual void derive1_fun1() {} };

对象的内存布局如下: 

1>  class Derive1	size(24):
1> +---
1> | +--- (base class Base1)
1> 0 | | {vfptr}
1> 8 | | base1_var1
1> 12 | | base1_var2
1> | +---
1> 16 | derive1_var1
1> 20 | derive1_var2
1> +---

对象的内存布局没有改变,改变的仍然是虚函数表,类似于在类Derive1中定义了如下类似的伪代码:  

void* vtable[] = { &Derive1::base1_fun1, &Base1::base1_fun2,&Derive1::derive1_fun1 };

const void** vfptr = &vtable[0];

可以看到,在虚函数表中追加了&Derive1::derive1_fun1()函数。  

好了,关于对象的布局我们就简单的介绍到这里,因为毕竟不是在研究C++,只要够我们研究HotSpot时使用就够了,更多关于内存布局的知识请参考其它文章或书籍。

HotSpot二分模型(1)的更多相关文章

  1. HotSpot类模型之ArrayKlass

    上一篇分析了 HotSpot类模型之InstanceKlass ,这次主要分析表示java数组类型的C++类. 1.ArrayKlass类 ArrayKlass继承自Klass,是所有数组类的抽象基类 ...

  2. HotSpot类模型之InstanceKlass

    上一篇 HotSpot源码分析之类模型 介绍了类模型的基础类Klass的重要属性及方法,这一篇介绍一下InstanceKlass及InstanceKlass的子类. 1.InstanceKlass类 ...

  3. HotSpot的类模型(2)

    在前一篇文章 HotSpot的二分模型中已经讲过,HotSpot采用了OOP-Klass模型描述Java的类和对象.Klass模型采用Klass类及相关子类来表示具体的Java类,可以理解这些类为Ja ...

  4. HotSpot的类模型(3)

    上一篇 HotSpot的类模型(2) 介绍了类模型的基础类Klass的重要属性及方法,这一篇介绍一下InstanceKlass及InstanceKlass的子类. 2.InstanceKlass类 每 ...

  5. HotSpot的类模型(4)

    我们继续接着上一篇 HotSpot的类模型(3)分析,这次主要分析表示java数组的C++类. 4.ArrayKlass类 ArrayKlass继承自Klass,是所有数组类的抽象基类,类及重要属性的 ...

  6. HotSpot的对象模型(6)

    接着上一篇,我们继续来讲oopDesc相关的子类. 3.instanceOopDesc类 instanceOopDesc类的实例表示除数组对象外的其它对象.在HotSpot中,对象在内存中存储的布局可 ...

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

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

  8. 操作句柄Handle(7)

    可以将Handle理解成访问对象的一个“句柄”.垃圾回收时对象可能被移动(对象地址发生改变),通过Handle访问对象可以对使用者屏蔽垃圾回收细节. Handle涉及到的相关类的继承关系如下图所示. ...

  9. 句柄Handle的释放(8)

    本篇首先介绍几个与句柄分配与释放密切相关的类,然后重点介绍句柄的释放. 1.HandleArea.Area与Chunk 句柄都是在HandleArea中分配并存储的,类的定义如下: // Thread ...

随机推荐

  1. Java实现 LeetCode 22 括号生成

    22. 括号生成 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合. 例如,给出 n = 3,生成结果为: [ "((()))", &quo ...

  2. java实现猜算式

    题目:猜算式 你一定还记得小学学习过的乘法计算过程,比如: x 15 ------ 273 ------ 请你观察如下的乘法算式 *** x *** -------- *** *** *** ---- ...

  3. 【asp.net core 系列】4. 更高更强的路由

    0. 前言 在之前我们介绍了请求通过路由寻找到控制器,以及控制器与视图的数据流转.那么,我们回过头来,再看看路由的一些其他用法. 1. 路由属性(Route Attribute) 按照英文的直接翻译, ...

  4. 【Vlog】Jmeter之使用beanshell将json提取器中的多个值拼接为一个列表

    场景如下: json提取器返回了当前登录用户的所有好友id,然而下一个接口是把好友id拼成一个数组进行传参的,现需将所有的好友ID拼接起来,类似ID1,ID2,ID3......这样 beanshel ...

  5. python—模块与包

    模块: (一个.py文件就是一个模块module,模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能.) 模块分三种: 1.python标准库 2.第三方模块 3.应用程序自定义模块 ...

  6. MySQL 8.0权限认证(下)

    MySQL 8.0权限认证(下)   一.设置MySQL用户资源限制   通过设置全局变量max_user_connections可以限制所有用户在同一时间连接MySQL实例的数量,但此参数无法对每个 ...

  7. 在WinForms里嵌入MediaPlayer的一些版本问题, tlbimp导入, 以及不导入而纯用C#+字符串来动态调用.

    网上很多写使用WindowsMediaPlayer WMP控件的文章. 大多数都是从工具栏或COM导入. 最近正在做的CEF整合Asp.Net Core Blazor server side的过程中, ...

  8. @codechef - JADUGAR2@ Chef and Same Old Recurrence 2

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 定义 dp 序列: \[dp(1) = K\\ dp(n) = ...

  9. Oracle VM VirtualBox 连接 Centos7 minimal版

    概述: 本博客是系列博客,主要讲述在Windows环境下安装虚拟机,在虚拟机中安装lunix系统,在lunix下安装docker,在docker中安装并使用常用的开发软件,比如tomcat.redis ...

  10. 【01JMeter基础】测试计划

    测试计划 整个脚本的集合根目录,所有线程组集合的承载,可以添加线程组.测试片段.非测试元件.配置元件.监听器.定时器.前置/后置处理起器.断言等 一.用户自定义变量 可以使用在所有的线程组中,通过${ ...