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的源代码
关注公众号,有HotSpot源码剖析系列文章!