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源碼剖析系列文章!  

 

相關文章
相關標籤/搜索