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