第2.2篇-HotSpot VM源码分析之类模型
HotSpot VM采用了OOP-Klass模型描述Java的类和对象。Klass模型采用Klass类及相关子类的对象来描述具体的Java类。一般HotSpot VM 在加载Java的Class 文件时,会在方法区创建Klass实例,这个实例用来保存Java类的元数据,包括常量池、字段、方法等。
Klass模型中相关类的继承体系如下图所示。
Metadata是元数据类的基础类型,除了Klass会直接继承外,表示方法的Method与表示常量池的ConstantPool也会继承,这里只讨论Klass继承体系中涉及到的相关类。
整个Klass模型中涉及到的C++类主要提供了2个功能:
(1)提供C++层面的Java类型(包括Java类和Java数组)表示,也就是用C++类的实例来描述Java类型;
(2)Java方法动态分派。
这一篇文章重点介绍一下Klass这个基础类型。
一个Klass实例(注意是Klass实例表示Java类的元数据,所以不同的Java类就用不同的Klass实例表示)代表一个Java类的元数据(相当于java.lang.Class
对象)。所以Klass中要有描述Java类中常量池、字段、方法的能力,也就是能保存这些信息,同时还能提供一些函数供HotSpot VM的开发者操作这些信息。
下面重点介绍一下Klass类。
一个C++类Klass的实例表示一个Java类型的元数据(相当于java.lang.Class对象),主要提供了两个功能:
-
实现Java语言层面的类型;
-
提供Java多态方法的支持。
在HotSpot VM中,Java对象使用oop实例来表示,不提供任何虚函数功能,oop实例保存了对应Klass的指针,所有方法调用通过Klass完成并通过Klass获取类型信息,Klass基于C++的虚函数提供对Java多态的支持。
笔者一般在描述Java类型的对象时使用“对象”这个术语,描述C++类的对象时使用“实例”这个术语,同样,描述Java的方法时使用“方法”术语,描述C++的方法时使用“函数”术语。在描述C++类时,使用属性,而在描述Java类时使用字段。
Klass类及重要属性的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.hpp class Klass : public Metadata { // ... protected: // note: put frequently-used fields together at start of klass structure // for better cache behavior (may not make much of a difference but sure won't hurt) enum { _primary_super_limit = 8 }; // The "layout helper" is a combined descriptor of object layout. // For klasses which are neither instance nor array, the value is zero. // // For instances, layout helper is a positive number, the instance size. // This size is already passed through align_object_size and scaled to bytes. // The low order bit is set if instances of this class cannot be // allocated using the fastpath. // // For arrays, layout helper is a negative number, containing four // distinct bytes, as follows: // MSB:[tag, hsz, ebt, log2(esz)]:LSB // where: // tag is 0x80 if the elements are oops, 0xC0 if non-oops // hsz is array header size in bytes (i.e., offset of first element) // ebt is the BasicType of the elements // esz is the element size in bytes // This packed word is arranged so as to be quickly unpacked by the // various fast paths that use the various subfields. // // The esz bits can be used directly by a SLL instruction, without masking. // // Note that the array-kind tag looks like 0x00 for instance klasses, // since their length in bytes is always less than 24Mb. // // Final note: This comes first, immediately after C++ vtable, // because it is frequently queried. jint _layout_helper; // The fields _super_check_offset, _secondary_super_cache, _secondary_supers // and _primary_supers all help make fast subtype checks. See big discussion // in doc/server_compiler/checktype.txt // // Where to look to observe a supertype (it is &_secondary_super_cache for // secondary supers, else is &_primary_supers[depth()]. juint _super_check_offset; // Class name. Instance classes: java/lang/String, etc. Array classes: [I, // [Ljava/lang/String;, etc. Set to zero for all other kinds of classes. Symbol* _name; // Cache of last observed secondary supertype Klass* _secondary_super_cache; // Array of all secondary supertypes Array<Klass*>* _secondary_supers; // Ordered list of all primary supertypes Klass* _primary_supers[_primary_super_limit]; // java/lang/Class instance mirroring this class oop _java_mirror; // Superclass Klass* _super; // First subclass (NULL if none); _subklass->next_sibling() is next one Klass* _subklass; // Sibling link (or NULL); links all subklasses of a klass Klass* _next_sibling; // All klasses loaded by a class loader are chained through these links Klass* _next_link; // The VM's representation of the ClassLoader used to load this class. // Provide access the corresponding instance java.lang.ClassLoader. ClassLoaderData* _class_loader_data; // Access flags. The class/interface distinction is stored here. AccessFlags _access_flags; // Used when biased locking is both enabled and disabled for this type markOop _prototype_header; ... }
下表对各个属性进行了简单的介绍。
字段名 | 作用 |
_layout_helper |
对象布局的综合描述符。如果不是InstanceKlass或ArrayKlass,值为0。如果是InstantceKlass或 ArrayKlass时,这个值是个组合数字。 (1)对于InstanceKlass而言,组合数字中包含有表示对象的、以字节为单位的内存占用大小,也就是说InstanceKlass实例表示Java类,由这个Java类创建的对象所需要的内存大小。 (2)对于ArrayKlass而言,该值是一个组合数字,包含4部分,具体怎么组合和解析由子类实现:
|
_name | Java类型名称,如java/lang/String,[Ljava/lang/String; |
_primary_supers |
_primary_supers代表了这个类的父类,其类型是个Klass指针数组,默认的大小为8,可通过命令更改。例如IOException是Exception的子类, 而Exception又是Throwable的子类。所以表示IOException类的_primary_supers属性值为: [Throwable, Exception, IOException]。如果继承链过长,也就是当前类加上继承的父类多于8个时, 会将多出来的类存储到secondary_supers数组中 |
_super_check_offset |
快速查找supertype的一个偏移量,这个偏移量是相对于Klass实例起始地址的偏移量。如果当前表示的类是IOException, 那么这个属性就指向_primary_supers数组中存储IOException的位置。当存储的类多于8个时,值与secondary_super_cache 相等 |
_secondary_supers |
Klass指针数组,一般存储Java类实现的接口,偶尔还会存储Java类及其父类 |
_secondary_super_cache |
Klass指针,保存上一次查询父类的结果 |
_java_mirror | oopDesc类型的指针,保存的是当前Klass对象表示的Java类所对应的java.lang.Class对象,可以据此访问类的静态属性 |
_super | Klass指针,指向Java类的直接父类 |
_subklass | Klass指针,指向Java类的直接子类,由于直接子类可能有多个,所以通过_next_sibling连接起来 |
_next_sibling | Klass指针,该类的下一个子类,通过调用_subklass->next_sibling()获取_subklass的兄弟子类 |
_next_link | Klass指针,所有的由同一个类加载器加载的Java类都通过_next_link连接成一个单链表 |
_class_loader_data | ClassLoaderData指针,可以通过此属性找到加载该Java类的ClassLoader |
_access_flags | 获取Java类型的修饰符,如private、final、static、abstract 、native等。可区分出当前的Java类型是接口还是类。 |
_prototype_header | 在偏向锁的实现过程中非常重要,后续在介绍锁时会介绍 |
能够通过Klass类中的相关属性保存Java类型定义的一些信息,如_name保存Java类的名称、_super保存Java类型的父类等。Klass类是Klass模型中定义的C++类的基类,所以此类对象只保存了Java类型的一些必要信息,其它如常量池、方法、字段等会通过Klass类的具体子类的相关属性来保存。
类的属性比较多,我们在后面解析类的过程中可以看到对相关属性的赋值操作。
1、_layout_helper
_layout_helper是一个组合属性。如果当前的类表示一个Java数组类型时,这个属性的值比较复杂。通常会调用如下函数生成值:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.cpp jint Klass::array_layout_helper(BasicType etype) { bool isobj = (etype == T_OBJECT); int tag = isobj ? _lh_array_tag_obj_value : _lh_array_tag_type_value; // 在64位系统下,存放_metadata的空间大小是8字节,_mark是8字节, // length是4字节,对象头为20字节,由于要按8字节对齐,所以会填充4字节,最终占用24字节 int hsize = arrayOopDesc::base_offset_in_bytes(etype); // hsize表示数组头部大小 // Java元素类型需要占用的字节数 int esize = type2aelembytes(etype); // 例如Java基本类型元素占用4个字节,则存储的是2 int esz = exact_log2(esize); int lh = array_layout_helper(tag, hsize, etype, esz); return lh; }
表示Java数组类型的_layout_helper属性由4部分组成,下面分别介绍。
(1)tag
如果数组元素的类型为对象类型,值为0x80;否则值为0xC0,表示数组元素的类型为Java基本类型。其中用到2个枚举常量,如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.hpp _lh_array_tag_type_value = ~0x00, _lh_array_tag_obj_value = ~0x01
_lh_array_tag_type_value的二进制表示为32个1:11111111111111111111111111111111,其实也就是0xC0000000 >> 30,做算术右移,负数的最高位补1。
_lh_array_tag_obj_value的二进制表示为最高位31个1:11111111111111111111111111111110,其实也就是0x80000000 >> 30,做算术右移,负数的最高位补1。
(2)hsize
hsize表示数组头元素的字节数。调用arrayOopDesc::base_offset_in_bytes()及相关函数获取,实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/arrayOop.hpp static int base_offset_in_bytes(BasicType type) { return header_size(type) * HeapWordSize; } static int header_size(BasicType type) { size_t typesize_in_bytes = header_size_in_bytes(); return (int)(Universe::element_type_should_be_aligned(type) ? align_object_offset(typesize_in_bytes/HeapWordSize) : typesize_in_bytes/HeapWordSize); } // 在64位系统默认参数下,_metadata占用的内存大小是8字节,_mark是8字节, // length是4字节,对象头为20字节,由于要按8字节对齐,所以会填充4字节, // 最终占用24字节 static int header_size_in_bytes() { size_t hs = align_size_up( length_offset_in_bytes() + sizeof(int) , HeapWordSize ); return (int)hs; } static int length_offset_in_bytes() { return UseCompressedClassPointers ? klass_gap_offset_in_bytes() : sizeof(arrayOopDesc); }
在length_offset_in_bytes()函数中,使用-XX:+UseCompressedClassPointers选项来压缩类指针,默认的值为true。sizeof(arrayOopDesc)返回的值为16,其中_mark和_metadata._klass各点用8字节。在压缩指针的情况下,_mark占用8字节,_metadata._narrowKlass占用4字节,共12字节。
(3)etype与esize
etype表示数组元素的类型,esize表示数组元素的大小。
最终会在Klass::array_layout_helper()函数中调用array_layout_helper()函数完成属性值的计算。这个函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.hpp static jint array_layout_helper(jint tag, int hsize, BasicType etype, int log2_esize) { return (tag << _lh_array_tag_shift) // 左移30位 | (hsize << _lh_header_size_shift) // 左移16位 | ((int)etype << _lh_element_type_shift) // 左移8位 | (log2_esize << _lh_log2_element_size_shift); // 左移0位 }
最终计算出来的数组类型的_layout_helper值为负数,因为最高位为1,而对象类型通常是一个正数,这样就可以简单的通过判断_layout_helper值来区分数组和对象。_layout_helper最终的布局如下图所示。
对_lh_array_tag_type_value与_lh_array_tag_obj_value值左移30位后,第32位上肯定为1,所以最终计算出的值是一个小于0的数。而非数组类型,一般由InstanceKlass对象表示的Java类来说,计算的属性值如下:
static jint instance_layout_helper(jint size, bool slow_path_flag) { if(slow_path_flag){ return (size << LogHeapWordSize) | _lh_instance_slow_path_bit; }else{ return (size << LogHeapWordSize) | 0; // LogHeapWordSize=3 } }
size为对象的、以字节为单位的内存占用大小,所以肯定是一个正数。这样就可以通过_layout_helper来判断类型了。
2、_primary_supers、_super_check_offset、_secondary_supers与_secondary_super_cache
这几个属性完全是为了加快判定父子关系等逻辑而加入的。下面看initialize_supers()函数中是如何初始化这几个属性的。函数的第1部分实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.cpp void Klass::initialize_supers(Klass* k, TRAPS) { // 当前类的父类k可能为NULL,例如Object的父类为NULL if (k == NULL) { set_super(NULL); _primary_supers[0] = this; } // k就是当前类型的直接父类,如果有父类,那么super()获取的Klass::_super属性的值通常为NULL,因为还没有设置此属性的值。
else if (k != super() || k == SystemDictionary::Object_klass()) { assert(super() == NULL || super() == SystemDictionary::Object_klass(), "initialize this only once to a non-trivial value"); set_super(k); // 通过Klass::_super属性保存当前类型的父类 Klass* sup = k;
// 当sup存储在_secondary_supers数组中时,super_depth()返回默认的值8,否则返回在_primary_supers数组中存储的位置 int sup_depth = sup->super_depth(); // 调用primary_super_limit()函数得到的默认值为8 juint my_depth = MIN2(sup_depth + 1, (int)primary_super_limit()); // 当父类的继承链长度大于等于primary_super_limit()时,当前的深度只能是primary_super_limit(), // 也就是8,因为_primary_supers数组中最多只能保存8个类 if (!can_be_primary_super_slow()) my_depth = primary_super_limit(); // my_depth默认的值为8 // 将直接父类的继承类拷贝到_primary_supers中,因为直接父类和当前子类肯定有共同的继承链 for (juint i = 0; i < my_depth; i++) { _primary_supers[i] = sup->_primary_supers[i]; } Klass* *super_check_cell; if (my_depth < primary_super_limit()) { _primary_supers[my_depth] = this; // 将当前类也存储在_primary_supers中 super_check_cell = &_primary_supers[my_depth]; } else { 需要将部分父类放入_secondary_supers数组中 super_check_cell = &_secondary_super_cache; } // 设置Klass::_super_check_offset属性的值 set_super_check_offset((address)super_check_cell - (address) this); // 省略了第2部分代码 }
在设置当前类型的父类时通常都会调用initialize_supers()函数,同时也会设置_primary_supers与_super_check_offset属性的值,如果继承链过长,还有可能设置_secondary_supers、_secondary_super_cache属性的值。这些属性中保存的信息可以快速的进行类关系之间的判断,例如父子关系的判断。
函数的第2部分代码实现如下:
if (secondary_supers() == NULL) { KlassHandle this_kh (THREAD, this); int extras = 0; Klass* p; // 当p不为NULL并且p已经存储在了_secondary_supers数组中时,条件为true // 也就是当前类的父类多于8个,需要将多出来的存储到_secondary_supers数组中 for (p = super(); !(p == NULL || p->can_be_primary_super()); p = p->super()) { ++extras; } // 计算secondaries需要的大小,因为secondaries数组中还需要存储当前类型的 // 所有实现接口(包括直接和间接实现的接口) // 如果当前是接口时,那么计算出的extras为0,那么如下函数就会直接设置_secondary_supers为 // 所有的接口,然后返回NULL,所以接口通常会在这里返回 GrowableArray<Klass*>* secondaries = compute_secondary_supers(extras); if (secondaries == NULL) { return; } // 将无法存储在_primary_supers中的类暂时存储在primaries中 GrowableArray<Klass*>* primaries = new GrowableArray<Klass*>(extras); for (p = this_kh->super(); !(p == NULL || p->can_be_primary_super()); p = p->super()) { ... primaries->push(p); } int new_length = primaries->length() + secondaries->length(); Array<Klass*>* s2 = MetadataFactory::new_array<Klass*>(class_loader_data(), new_length, CHECK); int fill_p = primaries->length(); for (int j = 0; j < fill_p; j++) { // 这样的设置会让父类永远在s2数组的前面,子类在父类之后存储 s2->at_put(j, primaries->pop()); } for( int j = 0; j < secondaries->length(); j++ ) { // 类存储在s2数组前面,接口存储在数组的后面 s2->at_put(j+fill_p, secondaries->at(j)); } this_kh->set_secondary_supers(s2); // 设置_secondary_supers属性的值 }
可以看到,会将当前类型继承链中多于8个的父类存储到_secondary_supers数组中,不过因为继承链一般都不会多于8个,所以设置了默认值为8,避免过大的数组浪费太多的内存。
下面举个例子,看看这几个属性是如何存储值的,如下:
interface IA{} interface IB{} class A{} class B extends A{} class C extends B{} class D extends C{} public class Test extends D implements IA,IB {}
配置-XX:FastSuperclassLimit=3后,_primary_supers数组中就最多只能存储3个类了。值如下:
_primary_supers[Object,A,B] _secondary_supers[C,D,IA,IB]
由于当前类Test的继承链过长,导致C和D只能存储到_secondary_supers。所以此时_super_check_offset会指向C,也就是_secondary_supers中存储的第1个元素。
再举个例子,如下:
interface IA{} interface IB extends IA{} public interface Test extends IB{}
假设当前的Test为接口,那么值如下:
_primary_supers[Object] _secondary_supers[IA,IB]
下面举个例子,看一下这几个属性如何应用。例如is_subtype_of()函数,实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.hpp // 判断当前类是否为k的子类。k可能为接口,如果当前类型实现了k接口,函数也返回true bool is_subtype_of(Klass* k) const { juint off = k->super_check_offset(); Klass* sup = *(Klass**)( (address)this + off ); const juint secondary_offset = in_bytes(secondary_super_cache_offset()); // 如果k在_primary_supers中,那么利用_primary_supers一定能判断出 // k与当前类的父子关系 if (sup == k) { return true; } // 如果k存储在_secondary_supers中, // 如果两者有父子关系,那么_super_check_offset需要与_secondary_super_cache相等 else if (off != secondary_offset) { return false; } // 可能有父子关系,需要进一步判断 else { return search_secondary_supers(k); } }
当通过_super_check_offset获取到的类与k相同时,那么k存在于当前类的继承链上,肯定有父子关系。
如果k存在于_primary_supers数组中,那么通过_super_check_offset就可快速判断,如果k存在于_secondary_supers中,那么需要调用search_secondary_supers()来判断。
调用的search_secondary_supers()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.cpp bool Klass::search_secondary_supers(Klass* k) const { if (this == k) return true; // 通过_secondary_supers中存储的信息进行判断 int cnt = secondary_supers()->length(); for (int i = 0; i < cnt; i++) { if (secondary_supers()->at(i) == k) { ((Klass*)this)->set_secondary_super_cache(k); return true; } } return false; }
可以看到,属性_secondary_super_cache保存了这一次父类查询的结果。查询的逻辑很简单,遍历_secondary_supers数组中的值并比较即可。
3、_super、_subklass、_next_sibling
由于Java类是单继承,所以可通过_super、_subklass、_next_sibling属性可直接找到当前类型的父类型或所有子类型。调用Klass::append_to_sibling_list()函数设置_next_sibling与_subklass属性的值,函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/oops/klass.cpp void Klass::append_to_sibling_list() { InstanceKlass* super = superklass(); // 获取到_super属性的值 if (super == NULL) return; // 如果Klass实例表示的是Object类时,此类没有超类 // super可能有多个子类,多个子类会用_next_sibling属性连接成单链表,当前的类是链表头元素 Klass* prev_first_subklass = super->subklass_oop(); // 获取_subklass属性的值 if (prev_first_subklass != NULL) { set_next_sibling(prev_first_subklass); // 设置_next_sibling属性的值 } super->set_subklass(this); // 设置_subklass属性的值 }
函数的实现逻辑很简单,这里不过多介绍。