HotSpot的物件模型(6)
接著上一篇,我們繼續來講oopDesc相關的子類。
3、instanceOopDesc類
instanceOopDesc類的例項表示除陣列物件外的其它物件。在HotSpot中,物件在記憶體中儲存的佈局可以分為三塊區域:物件頭(header)、物件欄位資料(field data)和對齊填充(padding),如下圖所示。
下面詳細介紹一下這3個組成部分。
1.物件頭
可以看到物件頭分為兩個部分,一個就是“Mark Word”,另外還有儲存指向方法區物件型別資料的指標_klass或_compressed_klass。這兩個都在介紹oopDesc類時詳細介紹過,這裡不再介紹。
2.物件欄位資料
物件欄位資料儲存Java原始碼中定義的各種型別欄位內容,具體包括父類繼承及子類定義的物件欄位。
儲存順序受到HotSpot分配策略引數(FieldAllocationStyle)和欄位在Java原始碼中定義順序的影響。預設分配策略為:long/double、int、short/char、boolean、oops(物件指標,32位系統佔用4位元組,64位系統佔用8位元組),可以看到,相同寬度的欄位總被分配到一起。
如果虛擬機器的-XX:+CompactFields引數為true,子類中較窄的變數可能插入到父類變數空隙中,以壓縮節省空間。例如,當碰到long/doubles時,會將一些短型別插入long/doubles和header的空隙中。(空隙:64位系統開啟壓縮指標,header佔12個位元組,剩下的4個位元組就是空隙。更多欄位儲存順序的內容將在第XX章詳細介紹。
3.對齊填充部分
對齊填充部分不是必須的,只起佔位符作用,沒有其他含義。HotSpot虛擬機器要求物件大小必須是8位元組的整數倍,物件頭是8位元組整數倍,所以填充是對例項資料沒有對齊的情況來說的。
在建立instanceOop物件時會呼叫allocate_instance()方法,這個方法的實現如下:
instanceOop InstanceKlass::allocate_instance(TRAPS) { int size = size_helper(); // Query before forming handle. KlassHandle h_k(THREAD, this); instanceOop i; i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL); return i; }
呼叫instanceKlass中的size_helper()方法獲取建立instanceOop物件所需要的記憶體大小,呼叫CollectedHeap::obj_allocate()方法分配size大小的記憶體。首先介紹size_helper()方法的實現,如下:
// Use this to return the size of an instance in heap words: int size_helper() const { return layout_helper_to_size_helper(layout_helper()); } int layout_helper() const { return _layout_helper; } static int layout_helper_to_size_helper(jint lh) { assert(lh > (jint)_lh_neutral_value, "must be instance"); // Note that the following expression discards _lh_instance_slow_path_bit. return lh >> LogHeapWordSize; }
從_layout_helper屬性中獲取大小,之前介紹過這個綜合描述符,如果為InstanceKlass,則組合數字中含有的是instanceOop物件的大小,在設定時呼叫的是instance_layout_helper()方法,如下:
static jint instance_layout_helper(jint size, bool slow_path_flag) { return (size << LogHeapWordSize) // LogHeapWordSize=3 | (slow_path_flag ? _lh_instance_slow_path_bit : 0); // 例項慢速分配有關 }
獲取size時需要向右移動3位即可。這個方法在建立InstanceKlass物件時會呼叫,不過size通常會初始化為0,在呼叫parseClassFile()方法計算完例項的大小時,還會呼叫此方法更新為真正需要的instanceOop物件大小,在解析類檔案時會詳細介紹例項大小的計算過程。
在InstanceKlass::allocate_instance()方法中呼叫CollectedHeap::obj_allocate()方法分配size大小的記憶體並將記憶體初始化為零值,方法將會在介紹垃圾回收時詳細介紹,這裡不介紹。
4、arrayOopDesc類
arrayOopDesc類的例項表示Java陣列物件。具體的基本型別陣列(物件)或物件型別陣列(物件)由具體的C++中定義的子類例項來表示。在HotSpot虛擬機器中,陣列物件在記憶體中儲存的佈局可以分為三塊區域:物件頭(header)、物件欄位資料(field data)和對齊填充(padding),如下圖所示。
與Java物件記憶體佈局唯一不同之處在於,陣列物件的物件頭中還會儲存陣列的長度length,佔用的記憶體空間為4位元組。在64位系統下,存放_metadata的空間大小是8位元組,_mark是8位元組,length是4位元組,物件頭為20位元組,由於要按8位元組對齊,所以會填充4位元組,最終佔用24位元組。64位開啟指標壓縮的情況下,存放_metadata的空間大小是4位元組,_mark是8位元組,length是4位元組,物件頭為16位元組。
5、arrayOopDesc類的子類
typeArrayOopDesc類的例項表示Java基本型別陣列(物件),objArrayOopDesc類的例項表示Java物件型別陣列(物件)。當需要建立typeArrayOopDesc物件時,通常會呼叫oopFactory類中定義的工廠方法,例如建立一個boolean陣列,則呼叫new_boolArray()方法,如下:
static typeArrayOop new_boolArray(int length, TRAPS) { TypeArrayKlass* tak = TypeArrayKlass::cast(Universe::boolArrayKlassObj()); return tak->allocate(length, CHECK_NULL); }
呼叫Universe::boolArrayKlassObj()方法獲取_charArrayKlassObj屬性的值,也就是之前介紹的、呼叫TypeArrayKlass::create_klass(T_BOOLEAN, sizeof(jboolean), CHECK)方法建立的表示boolean陣列的TypeArrayKlass物件,然後呼叫TypeArrayKlass中的allocate()方法建立typeArrayOop物件,如下:
typeArrayOop allocate(int length, TRAPS) { return allocate_common(length, true, THREAD); } typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) { assert(log2_element_size() >= 0, "bad scale"); if (length >= 0) { if (length <= max_length()) { size_t size = typeArrayOopDesc::object_size(layout_helper(), length); KlassHandle h_k(THREAD, this); typeArrayOop t; CollectedHeap* ch = Universe::heap(); if (do_zero) { t = (typeArrayOop)CollectedHeap::array_allocate(h_k, (int)size, length, CHECK_NULL); } else { t = (typeArrayOop)CollectedHeap::array_allocate_nozero(h_k, (int)size, length, CHECK_NULL); } return t; } } }
引數length表示建立陣列的大小,而do_zero表示是否需要在分配陣列記憶體時,將記憶體初始化為堆值。方法首先呼叫typeArrayOopDesc::object_size()方法從_layout_helper中獲取陣列的大小,方法的實現如下:
static int object_size(int lh, int length) { int instance_header_size = Klass::layout_helper_header_size(lh); int element_shift = Klass::layout_helper_log2_element_size(lh); julong size_in_bytes = length; size_in_bytes <<= element_shift; size_in_bytes += instance_header_size; // 按8位元組對齊,填充的一部分 julong size_in_words = ((size_in_bytes + (HeapWordSize-1)) >> LogHeapWordSize); return align_object_size((intptr_t)size_in_words); // 對齊,填充的一部分 }
之前介紹過,當為ArrayKlass時,_layout_helper屬性是個組合數字,呼叫 Klass::layout_helper_header_size()方法獲取陣列頭元素的位元組數;呼叫Klass::layout_helper_log2_element_size()方法獲取陣列元素的大小,對於陣列元素是boolean型別來說,這個值為1。
size = instance_header_size + length<<element_shift + 對齊填充
也就是物件頭加上例項資料,然後再加上對齊填充。
在TypeArrayKlass::allocate_common()方法中獲取到TypeArrayOopDesc物件所需要分配的記憶體大小後,就會呼叫CollectedHeap::array_allocate()或CollectedHeap::array_allocate_nozero()方法在堆上分配記憶體空間,關於在堆上分配記憶體的方法在後面介紹垃圾回收時會詳細介紹,這裡不介紹。
objArrayOop的建立與typeArrayOop的建立非常類似,也是呼叫oopFactory類中的工廠方法new_objectArray()方法,然後呼叫ObjArrayKlass::allocate()方法,這裡不在介紹。
相關文章的連結如下:
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)
關注公眾號,有HotSpot原始碼剖析系列文章!
&n