专注虚拟机与编译器研究

第2.4篇-HotSpot VM类模型之ArrayKlass

上一篇分析了 HotSpot类模型之InstanceKlass ,这次主要分析表示java数组类型的C++类。

1、ArrayKlass

ArrayKlass继承自Klass,是所有数组类的抽象基类,类及重要属性的定义如下: 

源代码位置:openjdk/hotspot/src/share/vm/oops/arrayKlass.hpp
  
class ArrayKlass: public Klass {
  ...
 private:
  int             _dimension;         // 当前实例表示的是n维度的数组
  Klass* volatile   _higher_dimension;  // 指向n+1维度的数组
  Klass* volatile   _lower_dimension;   // 指向n-1维度的数组
  int             _vtable_len;         //  vtable的大小
  oop            _component_mirror;  //   组件类型对应的java.lang.Class对象
  ...
}

在Klass的基础上增加的属性如下表所示。

字段 作用
_dimension int类型,表示数组的维度,记为n
_higher_dimension Klass指针,表示对n+1维数组Klass的引用
_lower_dimension Klass指针,表示对n-1维数组Klass的引用
_vtable_len int类型, 虚函数表的长度
_component_mirror

oop类型,保存数组的组件类型对应的java.lang.Class对象的oop

数组元素类型(Element Type)指的是数组去掉所有维度的类型,而数组的组件类型(Component Type)指的是数组去掉一个维度的类型。

_vtable_len的值为5,因为数组是对象类型,父类为Object类,而Object类中有5个虚方法可被用来继承和重写,如下:

void          finalize()
boolean       equals(Object)
String        toString()
int           hashCode()
Object        clone()

所以说数组类型也有从Object类继承下来的方法。

2、ArrayKlass类的子类

ArrayKlass的子类有表示数组组件类型是Java基本类型的TypeArrayKlass与表示组件类型是对象类型的ObjArrayKlass,这一节将介绍TypeArrayKlass

(1)TypeArrayKlass类

TypeArrayKlassArrayKlass类的子类,用于表示组件类型是Java基本类型的数组。类及重要属性的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.hpp

class TypeArrayKlass : public ArrayKlass {
  ...
 private:
  jint _max_length;  
  ...
}

_max_length属性保存数组允许的最大长度。数组类和普通类不同,数组类没有对应的Class文件,所以数组类是直接被HotSpot VM创建出来的。HotSpot VM在初始化时就会创建好Java8个基本类型的一维数组实例TypeArrayKlass。之前在介绍HotSpot VM启动时介绍过initializeJVM()函数,这个函数会调用到Universe::genesis()函数,在Universe::genesis()函数中初始化基本类型的一维数组实例TypeArrayKlass。例如初始化boolean类型的一维数组,调用语句如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/universe.cpp

_boolArrayKlassObj = TypeArrayKlass::create_klass(T_BOOLEAN, sizeof(jboolean), CHECK);

其中_boolArrayKlassObjUniverse类中定义的静态属性,如下:

Klass* Universe::_boolArrayKlassObj = NULL;

调用TypeArrayKlass::create_klass()函数创建TypeArrayKlass实例,函数的实现如下

源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.hpp

static inline Klass* create_klass(BasicType type, int scale, TRAPS) {
    TypeArrayKlass* tak = create_klass(type, external_name(type), CHECK_NULL);
    return tak;
}

调用另外一个TypeArrayKlass::create_klass()函数创建TypeArrayKlass实例,函数的实现如下

源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.cpp

TypeArrayKlass* TypeArrayKlass::create_klass(BasicType type,
                                      const char* name_str, TRAPS) {
  // 在HotSpot VM中,所有的字符串都通过Symbol实例来表示,以达到重用的目的
  Symbol* sym = NULL;
  if (name_str != NULL) {
    sym = SymbolTable::new_permanent_symbol(name_str, CHECK_NULL);
  }
  // 使用启动类加载器加载数组类型
  ClassLoaderData* null_loader_data = ClassLoaderData::the_null_class_loader_data();
  // 创建TypeArrayKlass并完成部分属性的初始化
  TypeArrayKlass* ak = TypeArrayKlass::allocate(null_loader_data, type, sym, CHECK_NULL);

  null_loader_data->add_class(ak);

  // 初始化TypeArrayKlass中的属性
  complete_create_array_klass(ak, ak->super(), CHECK_NULL);

  return ak;
}

如上函数中调用了2个方法完成TypeArrayKlass实例的创建和属性的初始化,下面分别进行介绍。

1、TypeArrayKlass::allocate()函数

函数的实现如下:

TypeArrayKlass* TypeArrayKlass::allocate(ClassLoaderData* loader_data, 
   BasicType type, Symbol* name, TRAPS) {

  int size = ArrayKlass::static_size(TypeArrayKlass::header_size());

  return new (loader_data, size, THREAD) TypeArrayKlass(type, name);
}

首先获取TypeArrayKlass实例需要占用内存的大小size,然后通过重载new运算符为对象内存分配,最后调用TypeArrayKlass的构造函数初始化相关属性。

header_size()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.hpp

static int header_size()  { 
  return sizeof(TypeArrayKlass)/HeapWordSize;
}

static_size()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/arrayKlass.hpp

int ArrayKlass::static_size(int header_size) {
  header_size = InstanceKlass::header_size();
  int vtable_len = Universe::base_vtable_size();
  int size = header_size + align_object_offset(vtable_len);
  return align_object_size(size);
}

注意header_size属性的值应该是TypeArrayKlass这个类自身占用的内存大小,但是现在却取的是InstanceKlass这个类自身占用内存的大小。这是因为InstanceKlass占用内存大小比TypeArrayKlass大,有足够内存存放相关数据,更重要的是为了统一从固定的偏移位置取出vtable等信息。在实际操作Klass实例的过程中,无需关心是数组还是类,直接偏移固定位置后就可获取。之前介绍过InstanceKlass实例的内存布局,TypeArrayKlass的布局相对简单,如下图所示。

相对InstanceKlass实例的内存布局来说,TypeArrayKlass实例的布局简单了许多,ObjectArrayKlass实例的布局也和TypeArrayKlass一样。

TypeArrayKlass的构造函数如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.cpp

TypeArrayKlass::TypeArrayKlass(BasicType type, Symbol* name) : ArrayKlass(name) {
  int lh = array_layout_helper(type);
  set_layout_helper(lh);
 
  set_max_length(arrayOopDesc::max_array_length(type)); // 设置数组的最大长度
  ...
}

设置TypeArrayKlass类中的_layout_helper_max_length属性值。调用Klass::array_layout_helper()函数获取_layout_helper属性的值,这个函数已经在2.1.1节介绍过,为了方便阅读,这里再次给出实现代码:

源代码位置:openjdk/hotspot/src/share/vm/oops/klass.cpp

jint Klass::array_layout_helper(BasicType etype) {
  int  hsize = arrayOopDesc::base_offset_in_bytes(etype);  
  int  esize = type2aelembytes(etype);
  bool isobj = (etype == T_OBJECT);
  int  tag   =  isobj ? _lh_array_tag_obj_value : _lh_array_tag_type_value;
  int lh = array_layout_helper(tag, hsize, etype, exact_log2(esize));

  return lh;
}

由于T_BOOLEAN为基本类型,所以tag的值取0xC0hsize调用arrayOopDesc::base_offset_in_bytes()函数获取,值为16,数组对象由对象头、对象字段数据和对齐填充组成,这里获取的就是对象头的大小;esize表示对应类型存储所需要的字节数,对于T_BOOLEAN来说,只需要1个字节即可。最后调用array_layout_helper()函数按照约定组合成一个int类型的数字并返回。

2、ArrayKlass::complete_create_array_klass()函数

函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/arrayKlass.hpp

void ArrayKlass::complete_create_array_klass(ArrayKlass* k, KlassHandle super_klass, TRAPS) {
  ResourceMark rm(THREAD);
  k->initialize_supers(super_klass(), CHECK);
  k->vtable()->initialize_vtable(false, CHECK);
  java_lang_Class::create_mirror(k, Handle(NULL), CHECK);
}

在第2.1.1节介绍Klass类时详细介绍过initialize_supers()函数,这个函数会初始化_primary_supers_super_check_offset等属性。此函数还会初始化vtable表,vtable将在6.3节介绍。调用java_lang_Class::create_mirror()函数对_component_mirror属性进行设置。函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/javaClasses.cpp

oop java_lang_Class::create_mirror(KlassHandle k, Handle protection_domain, TRAPS) {
  ...
  if (SystemDictionary::Class_klass_loaded()) {
    Handle mirror = InstanceMirrorKlass::cast(
    SystemDictionary::Class_klass())->allocate_instance(k, CHECK_0);

    InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
    java_lang_Class::set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));

    if (k->oop_is_array()) { // k是ArrayKlass实例
      Handle comp_mirror;
      if (k->oop_is_typeArray()) { // k是TypeArrayKlass实例
        BasicType type = TypeArrayKlass::cast(k())->element_type();
        comp_mirror = Universe::java_mirror(type);
      } else { // k是ObjArrayKlass实例
        assert(k->oop_is_objArray(), "Must be");
        Klass* element_klass = ObjArrayKlass::cast(k())->element_klass();
        comp_mirror = element_klass->java_mirror();
      }

      ArrayKlass::cast(k())->set_component_mirror(comp_mirror());
      set_array_klass(comp_mirror(), k());
    } else {
      assert(k->oop_is_instance(), "Must be");
      ...
      // 初始化本地静态字段的值,静态字段存储在java.lang.Class对象中
      InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, CHECK_NULL);
    }
    return mirror();
  } else {
    ...
    return NULL;
  }
}

kTypeArrayKlass实例时,调用Universe::java_mirror()函数获取对应类型typemirror值;当kObjArrayKlass实例时,获取的是组件类型的_java_mirror属性值。另外此函数还初始化了java.lang.Class对象中静态字段的值,这样静态字段就可以正常使用了。

基本类型的mirror值在HotSpot VM启动时就会创建,如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/universe.cpp

void Universe::initialize_basic_type_mirrors(TRAPS) {
   ...
   // 创建表示基本类型的java.lang.Class对象,此对象用oop表示,所以_bool_mirror
   // 的类型为oop
   _bool_mirror = java_lang_Class::create_basic_type_mirror("boolean",T_BOOLEAN, CHECK);
   ...
}

调用的create_basic_type_mirror()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/javaClasses.cpp

oop java_lang_Class::create_basic_type_mirror(const char* basic_type_name, BasicType type, TRAPS) {
  oop java_class = InstanceMirrorKlass::cast(
SystemDictionary::Class_klass())->allocate_instance(NULL, CHECK_0);
  if (type != T_VOID) {
    Klass* aklass = Universe::typeArrayKlassObj(type);
    set_array_klass(java_class, aklass);
  }
  return java_class;
}

调用InstanceMirrorKlass实例(表示java.lang.Class类)的allocate_instance()函数创建oop(表示java.lang.Class对象),_component_mirror最终设置的就是这个oop。一维或多维数组的元素类型如果是对象,使用Klass实例表示,例如Object[]的元素类型为Object,使用InstanceKlass实例表示;一维或多维数组的元素类型是基本类型时,没有对应的Klass实例,所以会使用java.lang.Class对象描述booleanint等类型,这样就会与oop对象(表示java.lang.Class对象)产生关系,相关属性的指向如图2-6所示。

2-6 TypeArrayKlassoop的关系

可以在oop中通过_array_klass_offset保存的偏移找到对应的TypeArrayKlass实例。

(2)ObjArrayKlass类

ObjArrayKlass是ArrayKlass的子类,用于表示数组元素是类或者数组

class ObjArrayKlass : public ArrayKlass {
  ...
 private:
  Klass* _element_klass;            // The klass of the elements of this array type
  Klass* _bottom_klass;             // The one-dimensional type (InstanceKlass or TypeArrayKlass)
  ...
}

该类新增了2个属性,如下:

  •  _element_klass:数组元素对应的Klass对象,如果是多维数组,对应数组元素的ObjArrayKlass对象
  •  _bottom_klass:一维数组的类型,可以是InstanceKlass或者TypeArrayKlass。一维基本类型数组为TypeArrayKlass,而二维基本类型数组就会使用ObjArrayKlass来表示,所以其_bottom_klass会是TypeArrayKlass。 

HotSpot VM在Universe::genesis()函数中创建Object数组,如下: 

源代码位置:openjdk/hotspot/src/share/vm/memory/universe.cpp

// 强制类型转换为InstanceKlass*类型
InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::Object_klass());
// 调用表示Object类的InstanceKlass实例的array_klass()函数
_objectArrayKlassObj = ik->array_klass(1, CHECK);

调用array_klass()函数时传递的参数1表示创建一维数组。调用表示Object类的InstanceKlass实例的函数创建的,所以Object数组的创建要依赖于InstanceKlass实例进行创建。

传递的参数1表示创建Object的一维数组类型,array_klass()函数及调用的相关函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/klass.hpp

Klass* array_klass(int rank, TRAPS) {  
  return array_klass_impl(false, rank, THREAD); 
}

调用的array_klass_impl()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp

Klass* InstanceKlass::array_klass_impl(bool or_null, int n, TRAPS) {
  instanceKlassHandle this_oop(THREAD, this);
  return array_klass_impl(this_oop, or_null, n, THREAD);
}

Klass* InstanceKlass::array_klass_impl(instanceKlassHandle this_oop, bool or_null, int n, TRAPS) {
  if (this_oop->array_klasses() == NULL) {
    if (or_null) return NULL;

    ResourceMark rm;
    JavaThread *jt = (JavaThread *)THREAD;
    {
      // 通过锁保证创建一维数组类型的原子性
      MutexLocker mc(Compile_lock, THREAD);   
      MutexLocker ma(MultiArray_lock, THREAD);

      if (this_oop->array_klasses() == NULL) {
        // 创建以当前InstanceKlass实例为元素类型的一维类型数组,创建成功后保存到
        // _array_klasses属性中,避免下次再重新创建
        Klass* k = ObjArrayKlass::allocate_objArray_klass(
                  this_oop->class_loader_data(), 1, this_oop, CHECK_NULL);
        this_oop->set_array_klasses(k);
      }
    }
  }

  // 已经创建好了以InstanceKlass实例为元素类型的一维数组,继续调用
  // 如下相关函数创建符合要求的n维数组
  // 如果dim+1维的ObjArrayKlass仍然不等于n,则会间接递归调用本
  // 函数继续创建dim+2、dim+3等,直到等于n
  ObjArrayKlass* oak = (ObjArrayKlass*)this_oop->array_klasses();
  if (or_null) {
    return oak->array_klass_or_null(n);
  }
  return oak->array_klass(n, CHECK_NULL);
}

表示Java类型的Klass实例在HotSpot VM中唯一,所以当_array_klassNULL时,表示首次以Klass为组件类型创建高一维的数组,创建成功后保存到_array_klass属性中,这样下次就可以直接调用array_klasses()函数获取即可。

现在创建Object一维数组的ObjArrayKlass实例,首次创建ObjTypeKlass时,InstanceKlass::_array_klasses属性的值为NULL,这样就会调用objArrayKlass::allocate_objArray_klass()函数,创建出一维的、元素类型为对象的数组并保存到了InstanceKlass::_array_klasses属性中。有了一维的引用类型数组后就可以接着调用array_klass_or_null()array_klass()函数创建n维的、元素类型为对象的数组了。

(1)创建一维的、元素类型为对象的数组ObjArrayKlass::allocate_objArray_klass()

函数的实现如下: 

源代码位置:openjdk/hotspot/src/share/vm/oops/objArrayKlass.cpp

// 创建出组合类型为element_klass的n维数组
Klass* ObjArrayKlass::allocate_objArray_klass(
 ClassLoaderData* loader_data,
 int  n,
 KlassHandle element_klass,
 TRAPS
) {

  ...

  // 为n维数组的ObjArrayKlass创建名称
  Symbol* name = NULL;
  if (!element_klass->oop_is_instance() ||
      (name = InstanceKlass::cast(element_klass())->array_name()) == NULL) {

    ResourceMark rm(THREAD);
    char *name_str = element_klass->name()->as_C_string();
    int len = element_klass->name()->utf8_length();
    char *new_str = NEW_RESOURCE_ARRAY(char, len + 4);
    int idx = 0;
    new_str[idx++] = '[';
    if (element_klass->oop_is_instance()) { 
      new_str[idx++] = 'L';
    }
    memcpy(&new_str[idx], name_str, len * sizeof(char));
    idx += len;
    if (element_klass->oop_is_instance()) {
      new_str[idx++] = ';';
    }
    new_str[idx++] = '\0';
    name = SymbolTable::new_permanent_symbol(new_str, CHECK_0);
    if (element_klass->oop_is_instance()) {
      InstanceKlass* ik = InstanceKlass::cast(element_klass());
      ik->set_array_name(name);
    }
  }

  // 创建出组合类型为element_klass,维度为n,名称为name的数组
  ObjArrayKlass* oak = ObjArrayKlass::allocate(loader_data, n, element_klass, name, CHECK_0);

  // 将创建出的类型加到类加载器列表中,在GC时会当作GC Roots处理
  loader_data->add_class(oak);

  ArrayKlass::complete_create_array_klass(oak, super_klass, CHECK_0);

  return oak;
}

allocate_objArray_klass()函数的参数element_klass的类型有可能为TypeArrayKlassInstanceKlassObjArrayKlass。最终会调用ObjArrayklass::allocate()函数创建一个组合类型为element_klass,维度为n,名称为nameObjArrayKlass。例如TypeArrayKlass表示一维byte数组,n表示2name就是[[B,最终会创建出对应的ObjArrayKlass实例。最后还会调用ArrayKlass::complete_create_array_klass()函数完成_component_mirror等属性的设置,这个函数在之前已经介绍过,这里不再介绍。

调用的ObjArrayKlass::allocate()函数的实现如下:

 

源代码位置:openjdk/hotspot/src/share/vm/oops/objArrayKlass.cpp

ObjArrayKlass* ObjArrayKlass::allocate( 
 ClassLoaderData* loader_data,
 int n, 
 KlassHandle klass_handle, 
 Symbol* name, 
 TRAPS
) {

  int size = ArrayKlass::static_size(ObjArrayKlass::header_size());

  return new (loader_data, size, THREAD) ObjArrayKlass(n, klass_handle, name);
}

 

首先需要调用ArrayKlass::static_size()函数计算出ObjArrayKlass实例所需要的内存大小,然后调用new重载运算符从Metaspace中分配指定大小的内存,最后调用ObjArrayKlass的构造函数初始化相关属性。

调用的ArrayKlass::static_size()函数的实现如下:

int ArrayKlass::static_size(int header_size) {
  header_size = InstanceKlass::header_size();
  int vtable_len = Universe::base_vtable_size();
  int size = header_size + align_object_offset(vtable_len);
  return align_object_size(size);
}

需要注意对ArrayKlass实例内存大小的计算逻辑,如上函数在计算header_size时获取的是InstanceKlass本身占用的内存大小,而不是ArrayKlass本身占用的内存大小,这是因为InstanceKlass本身占用的内存大小比ArrayKlass大,所以以InstanceKlass本身占用的内存大小为标准进行统一操作,在不区分Klass实例的具体类型时,只要偏移InstanceKlass::header_size()后,就可以获取vtable等信息。

ArrayKlass::complete_create_array_klass()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/oops/arrayKlass.cpp

void ArrayKlass::complete_create_array_klass(ArrayKlass* k, KlassHandle super_klass, TRAPS) {
  ResourceMark rm(THREAD);
  // 初始化数组类型的父类
  k->initialize_supers(super_klass(), CHECK);
  // 初始化虚函数表
  k->vtable()->initialize_vtable(false, CHECK);
  java_lang_Class::create_mirror(k, Handle(NULL), CHECK);
}

调用initialize_vtable()函数完成虚函数表的初始化,虚函数表将在6.3节详细介绍;调用java_lang_Class::create_mirror()函数完成当前ObjTypeArray实例对应的java.lang.Class对象的创建并设置相关属性,函数已经在之前介绍过,这里不再介绍。

举个例子,表示Object类的InstanceKlass实例与表示一维数组Object[]ObjArrayKlass实例之间的相关属性指向如下图所示。

如果InstanceKlass实例表示java.lang.Object类,那么_array_name的值为"[Ljava/lang/Object;"  

(2)创建n维的、元素类型为对象类型的数组ObjArrayKlass::array_klass()

这里要提醒一下,如果创建n+1的基本类型数组,那么其实也就是创建n维的、元素类型为一维基本类型的对象类型的数组。

InstanceKlass::array_klass_impl()函数中,如果创建好了一维类型的数组,依据这个一维类型数组可以创建出n维类型数组,无论调用array_klass()函数还是array_klass_or_null()函数,都会调用到array_klass_impl()函数,此函数的实现如下:

 

源代码位置:openjdk/hotspot/src/share/vm/oops/objArrayKlass.cpp

Klass* ObjArrayKlass::array_klass_impl(bool or_null, TRAPS) {
  // 创建出比当前维度多一个维度的数组
  return array_klass_impl(or_null, dimension() +  1, CHECK_NULL);
}

Klass* ObjArrayKlass::array_klass_impl(bool or_null, int n, TRAPS) {

  int dim = dimension();
  if (dim == n) 
    return this;

  if (higher_dimension() == NULL) {
    if (or_null)  return NULL;

    ResourceMark rm;
    JavaThread *jt = (JavaThread *)THREAD;
    {
      MutexLocker mc(Compile_lock, THREAD);   
      // Ensure atomic creation of higher dimensions
      MutexLocker mu(MultiArray_lock, THREAD);

      if (higher_dimension() == NULL) {
        // 以当前的ObjArrayKlass实例为组件类型,创建比当前dim维度多一维度的数组
        Klass* k =  ObjArrayKlass::allocate_objArray_klass(
        class_loader_data(), dim + 1, this, CHECK_NULL);
        ObjArrayKlass* ak = ObjArrayKlass::cast(k);
        ak->set_lower_dimension(this);
        OrderAccess::storestore();
        set_higher_dimension(ak);
      }
    }
  } 

  // 如果dim+1维的ObjArrayKlass仍然不是n维的,则会间接递归调用本函数继续
  // 创建dim+2、dim+3等ObjArrayKlass实例,直到此实例的维度等于n
  ObjArrayKlass *ak = ObjArrayKlass::cast(higher_dimension());
  if (or_null) {
    return ak->array_klass_or_null(n);
  }
  return ak->array_klass(n, CHECK_NULL);
}

 

多维数组会间接递归调用如上函数创建出符合维度要求的数组类型。表示Java类的InstanceKlass实例与以此Java类为基本类型的一维与二维数组之间的关系如图2-8所示。

二维数组Object[][]、一维数组Object[]Object类之间的关系就符合上图之间的关系。

 

posted on 2020-11-23 07:44  鸠摩(马智)  阅读(739)  评论(0编辑  收藏  举报

导航