HotSpot二分模型(1)

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

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

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.)oop

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

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

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

一、只含有数据成员的对象 指针

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>  	+---

能够看到,成员变量是按照定义的顺序来保存的,类对象的大小就是全部成员变量的大小之和。 code

二、没有虚函数的对象orm

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>  	+---

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

三、含有虚函数的对象 

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];

  

四、继承类对象

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时使用就够了,更多关于内存布局的知识请参考其它文章或书籍。

 

 

相关文章的连接以下:

 

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

 

二、调试HotSpot源代码

 

三、HotSpot项目结构 

 

四、HotSpot的启动过程 

 

五、HotSpot二分模型 (1)

 

六、HotSpot的类模型(2)  

 

七、HotSpot的类模型(3) 

 

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

 

相关文章
相关标签/搜索