接着上一篇,我們繼續來講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的源代碼
關注公眾號,有HotSpot源碼剖析系列文章!