第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类
TypeArrayKlass是ArrayKlass类的子类,用于表示组件类型是Java基本类型的数组。类及重要属性的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/TypeArrayKlass.hpp class TypeArrayKlass : public ArrayKlass { ... private: jint _max_length; ... }
_max_length属性保存数组允许的最大长度。数组类和普通类不同,数组类没有对应的Class文件,所以数组类是直接被HotSpot VM创建出来的。HotSpot VM在初始化时就会创建好Java中8个基本类型的一维数组实例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);
其中_boolArrayKlassObj是Universe类中定义的静态属性,如下:
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的值取0xC0;hsize调用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; } }
当k是TypeArrayKlass实例时,调用Universe::java_mirror()函数获取对应类型type的mirror值;当k为ObjArrayKlass实例时,获取的是组件类型的_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对象描述boolean、int等类型,这样就会与oop对象(表示java.lang.Class对象)产生关系,相关属性的指向如图2-6所示。
图2-6 TypeArrayKlass与oop的关系
可以在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_klass为NULL时,表示首次以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的类型有可能为TypeArrayKlass,InstanceKlass或ObjArrayKlass。最终会调用ObjArrayklass::allocate()函数创建一个组合类型为element_klass,维度为n,名称为name的ObjArrayKlass。例如TypeArrayKlass表示一维byte数组,n表示2、name就是[[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类之间的关系就符合上图之间的关系。