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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM