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

     

  • 相关阅读:
    2018牛客网暑期ACM多校训练营(第九场)A -Circulant Matrix(FWT)
    ZOJ
    BZOJ 4318 OSU!(概率DP)
    POJ
    POJ
    Linux安装及管理程序
    Linux目录及文件管理
    linux账号管理操作
    linux系统命令总结
    linux目录及文件管理操作
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13291427.html
Copyright © 2020-2023  润新知