专注虚拟机与编译器研究

第2.3篇-HotSpot VM类模型之InstanceKlass

上一篇 HotSpot源码分析之类模型 介绍了类模型的基础类Klass的重要属性及方法,这一篇介绍一下InstanceKlass及InstanceKlass的子类。

1、InstanceKlass类

每个InstanceKlass实例表示一个具体的Java类型(这里的Java类型不包括Java数组类型)。InstanceKlass类及重要属性的定义如下:

class InstanceKlass: public Klass {
 ...
 protected:
  // Annotations for this class
  Annotations*       _annotations;
  // Array classes holding elements of this class.
  Klass*             _array_klasses;
  // Constant pool for this class.
  ConstantPool*     _constants;
  // The InnerClasses attribute and EnclosingMethod attribute. The
  // _inner_classes is an array of shorts. If the class has InnerClasses
  // attribute, then the _inner_classes array begins with 4-tuples of shorts
  // [inner_class_info_index, outer_class_info_index,
  // inner_name_index, inner_class_access_flags] for the InnerClasses
  // attribute. If the EnclosingMethod attribute exists, it occupies the
  // last two shorts [class_index, method_index] of the array. If only
  // the InnerClasses attribute exists, the _inner_classes array length is
  // number_of_inner_classes * 4. If the class has both InnerClasses
  // and EnclosingMethod attributes the _inner_classes array length is
  // number_of_inner_classes * 4 + enclosing_method_attribute_size.
  Array<jushort>*   _inner_classes;
 
  // Array name derived from this class which needs unreferencing
  // if this class is unloaded.
  Symbol*           _array_name;
 
  // Number of heapOopSize words used by non-static fields in this klass
  // (including inherited fields but after header_size()).
  int               _nonstatic_field_size;
  int               _static_field_size;    // number words used by static fields (oop and non-oop) in this klass
  // Constant pool index to the utf8 entry of the Generic signature,
  // or 0 if none.
  u2                _generic_signature_index;
  // Constant pool index to the utf8 entry for the name of source file
  // containing this klass, 0 if not specified.
  u2                _source_file_name_index;
  u2                _static_oop_field_count;// number of static oop fields in this klass
  u2                _java_fields_count;    // The number of declared Java fields
  int               _nonstatic_oop_map_size;// size in words of nonstatic oop map blocks
 

  u2                _minor_version;  // minor version number of class file
  u2                _major_version;  // major version number of class file
  Thread*           _init_thread;    // Pointer to current thread doing initialization (to handle recusive initialization)
  int               _vtable_len;     // length of Java vtable (in words)
  int               _itable_len;     // length of Java itable (in words)
  OopMapCache*      volatile _oop_map_cache;   // OopMapCache for all methods in the klass (allocated lazily)
  JNIid*            _jni_ids;              // First JNI identifier for static fields in this class
  jmethodID*        _methods_jmethod_ids;  // jmethodIDs corresponding to method_idnum, or NULL if none
  nmethodBucket*    _dependencies;         // list of dependent nmethods
  nmethod*          _osr_nmethods_head;    // Head of list of on-stack replacement nmethods for this class

 
  // Class states are defined as ClassState (see above).
  // Place the _init_state here to utilize the unused 2-byte after
  // _idnum_allocated_count.
  u1                _init_state;                    // state of class
  u1                _reference_type;                // reference type
 

  // Method array.
  Array<Method*>*   _methods;
  // Default Method Array, concrete methods inherited from interfaces
  Array<Method*>*   _default_methods;
  // Interface (Klass*s) this class declares locally to implement.
  Array<Klass*>*    _local_interfaces;
  // Interface (Klass*s) this class implements transitively.
  Array<Klass*>*    _transitive_interfaces;

  // Int array containing the vtable_indices for default_methods
  // offset matches _default_methods offset
  Array<int>*       _default_vtable_indices;
 
  // Instance and static variable information, starts with 6-tuples of shorts
  // [access, name index, sig index, initval index, low_offset, high_offset]
  // for all fields, followed by the generic signature data at the end of
  // the array. Only fields with generic signature attributes have the generic
  // signature data set in the array. The fields array looks like following:
  //
  // f1: [access, name index, sig index, initial value index, low_offset, high_offset]
  // f2: [access, name index, sig index, initial value index, low_offset, high_offset]
  //      ...
  // fn: [access, name index, sig index, initial value index, low_offset, high_offset]
  //     [generic signature index]
  //     [generic signature index]
  //     ...
  Array<u2>*        _fields;
 
  // embedded Java vtable follows here
  // embedded Java itables follows here
  // embedded static fields follows here
  // embedded nonstatic oop-map blocks follows here
  // embedded implementor of this interface follows here
  //   The embedded implementor only exists if the current klass is an
  //   iterface. The possible values of the implementor fall into following
  //   three cases:
  //     NULL: no implementor.
  //     A Klass* that's not itself: one implementor.
  //     Itsef: more than one implementors.
  // embedded host klass follows here
  //   The embedded host klass only exists in an anonymous class for
  //   dynamic language support (JSR 292 enabled). The host class grants
  //   its access privileges to this class also. The host class is either
  //   named, or a previously loaded anonymous class. A non-anonymous class
  //   or an anonymous class loaded through normal classloading does not
  //   have this embedded field.
  
  ...
}

重要属性的介绍如下表所示。

字段名 作用
_annotations Annotations类型的指针,保存该类使用的所有注解
_array_klasses

数组元素为该类型的数组Klass指针,例如ObjArrayKlass实例表示的是数组且元素类型为Object时,那么表示Object类的InstanceKlass实例的_array_klasses是指向ObjArrayKlass实例的指针

_array_name

以该类型为数组组件类型(指的是数组去掉一个维度的类型)的数组的名字,如果当前InstanceKlass实例表示Object类,则名称为"[Ljava/lang/Object;"

_constants ConstantPool类型的指针,用来指向保存了Java类的常量池信息的ConstantPool实例
_inner_classes 用一个jushort数组保存当前类的InnerClasses属性和EnclosingMethod属性
_nonstatic_field_size

非静态字段需要占用的内存大小,以字为单位。在为InstanceKlass实例表示的Java类所创建的对象(使用oop表示)分配内存时,会参考此属性的值分配内存,这个值在类解析时会计算好

_static_field_size

静态字段需要占用的内存大小 ,以字为单位。在为该InstanceKlass实例表示的Java类创建对应的java.lang.Class对象(使用oop表示)时,根据获取此属性的值分配对象内存,这个值在类解析时会计算好

_generic_signature_index

保存Java类的签名在常量池中的索引

_source_file_name_index 保存Java类的源文件名在常量池中的索引
_static_oop_field_count Java类包含的静态引用类型字段的数量
_java_fields_count Java类包含的字段总数量
_nonstatic_oop_map_size OopMapBlock需要占用的内存大小,以字为单位。OopMapBlock使用<偏移量,数量>描述Java类型(InstanceKlass实例表示)中各个非静态对象(oop表示)类型的变量在Java对象中的具体位置,这样垃圾回收时就能找到Java对象中引用的其它对象
_minor_version 类的次版本号
_major_version 类的主版本号
_init_thread 执行Java类初始化的Thread指针
_vtable_len Java虚函数表(vtable)所占用的内存大小,以字为单位
_itable_len Java接口函数表(itable)所占用的内存大小,以字为单位
_oop_map_cache OopMapCache指针,该类的所有方法的OopMapCache
_jni_ids/_methods_jmethod_ids JNIid指针与jmethodID指针,这2个指针对于JNI方法操作属性和方法非常重要,在介绍JNI时会详细介绍。
_dependencies nmethodBucket指针,依赖的本地方法,以根据其_next属性获取下一个nmethod
_osr_nmethods_head 栈上替换的本地方法链表的头元素
_init_state

表示类的状态,值取自枚举类型ClassState中定义的常量,定义了如下常量值:

  • allocated(已分配内存)
  • loaded(从class文件读取加载到内存中)
  • linked(已经成功链接和校验)
  • being_initialized(正在初始化)
  • fully_initialized(已经完成初始化)
  • initialization_error(初始化异常)
_reference_type

当前的InstanceKlass实例表示的引用类型,可能是强引用、软引用、弱引用等

_methods 保存方法的指针数组
_default_methods 保存方法的指针数组,从接口继承的默认方法
_local_interfaces 保存接口的指针数组,直接实现的接口Klass
_transitive_interfaces 保存接口的指针数组,包含_local_interfaces和间接实现的接口
_default_vtable_indices 默认方法在虚函数表中的索引
_fields

类的字段属性,每个字段的6个属性accessname indexsig indexinitial value indexlow_offsethigh_offset组成一个元组access表示访问控制属性;根据name index可以获取属性名;根据initial value index可以获取初始值;根据low_offsethigh_offset可以获取该属性在内存中的偏移量。保存完如上所有属性之后还可能会保存泛型签名信息

InstanceKlass类与Klass类中定义的这些属性用来保存Java类元信息。在后续的类解析中会看到对相关属性的赋值操作。除了保存类元信息外,此类还有另外一个重要的功能,即支持方法分派,主要是通过Java虚函数表和Java接口函数表来完成的,不过C++并不像Java一样,保存信息时必须在类中定义出相关属性,C++只是在分配内存时为要存储的信息分配好特定的内存,然后直接通过内存偏移来操作即可。

接下来几个属性没有对应的属性名,不过可以通过指针加偏移量的方式访问:

  • Java vtable:Java虚函数表,大小等于_vtable_len;
  • Java itables:Java接口函数表,大小等于 _itable_len;
  • 非静态OopMapBlock,大小等于_nonstatic_oop_map_size当前类也会继承父类的属性,所以同样可能需要保存父类的OopMapBlock信息,这样当前的Klass实例可能会含有多个OopMapBlockGC在垃圾回收时,遍历某个对象所引用的其它对象时,会依据此信息进行查找;
  • 接口的实现类,只有当前Klass实例表示一个接口时才存在这个信息。如果接口没有任何实现类则为NULL;如果只有一个实现类则为该实现类的Klass指针;如果有多个实现类,则为当前接口本身;
  • host klass,只在匿名类中存在,为了支持JSR 292中的动态语言特性,会给匿名类生成一个host klass。 

HotSpot VM在解析一个类时会通过InstanceKlass实例保存元数据信息,调用InstanceKlass::allocate_instance_klass()函数为具体的InstanceKlass实例分配内存,而分配多大的内存则是通过调用InstanceKlass::size()函数计算出来的,调用语句如下:

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

int size = InstanceKlass::size(vtable_len,itable_len,nonstatic_oop_map_size,isinterf,is_anonymous);

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

static int size(
  int    vtable_length,
  int    itable_length,
  int    nonstatic_oop_map_size,
  bool   is_interface,
  bool   is_anonymous
){
return  align_object_size(
           header_size()    +  // InstanceKlass类本身占用的内存大小
	   align_object_offset(vtable_length) +
	   align_object_offset(itable_length) +
	   //    [EMBEDDED nonstatic oop-map blocks] size in words = nonstatic_oop_map_size
	   //      The embedded nonstatic oop-map blocks are short pairs (offset, length)
	   //      indicating where oops are located in instances of this klass.
	   (
			  (is_interface || is_anonymous) ?
			  align_object_offset(nonstatic_oop_map_size) :
			  nonstatic_oop_map_size
	   ) +
	   //    [EMBEDDED implementor of the interface] only exist for interface
	   (
			   is_interface ? (int)sizeof(Klass*)/HeapWordSize : 0
	   ) +
	   //    [EMBEDDED host klass        ] only exist for an anonymous class (JSR 292 enabled)
	   (
			   is_anonymous ? (int)sizeof(Klass*)/HeapWordSize : 0
            )
	);
}

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

函数返回值就是此次创建Klass实例所需要开辟的内存大小。由此函数的计算逻辑可以看出Klass实例的内存布局如下图所示。

  

Klass本身占用的内存大小其实主要就是类中声明的实例变量,不过如果类中定义了虚函数,依据C++类实例的内存布局,还需要为一个指向C++虚函数表的指针预留存储空间。图中的灰色部分是可选的,主要是依据实际情况决定。为vtableitable以及nonstatic_oop_map分配的内存大小在类解析的过程中会计算好,在第4章介绍类解析过程中会详细介绍。

调用的header_size()函数计算Klass本身占用的内存大小,实现如下:

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

static int header_size(){ 
// HeapWordSize在64位系统下值为8,也就是一个字的大小,同时也是
// 一个非压缩指针占用的内存大小
  return align_object_offset(sizeof(InstanceKlass)/HeapWordSize);  
}

调用的align_object_offset()函数进行内存对齐,方便对内存进行高效操作,这是一块非常重要的C++知识点,后面会专门进行讲解。 

2、InstanceKlass类的子类

InstanceKlass共有3个直接子类,这3个子类用来表示一些特殊的类,下面简单介绍一下这3个子类:

(1)InstanceRefKlass

表示Java引用类型的java.lang.ref.Reference类需要使用C++InstanceRefKlass的实例来表示,在创建这个类的实例时,_reference_type字段(定义在InstanceKlass类中)的值通常会java.lang.ref.Reference类表示的是哪种引用类型。值通过枚举类进行定义,如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/referenceType.hpp

enum ReferenceType {
  REF_NONE,      // 普通类,也就是非引用类型
  REF_OTHER,     // 表示java/lang/ref/Reference子类,但是不包括如下的几个常见引用类型
  REF_SOFT,      // 表示java/lang/ref/SoftReference类及其子类
  REF_WEAK,      // 表示java/lang/ref/WeakReference类及其子类
  REF_FINAL,     // 表示java/lang/ref/FinalReference类及其子类
  REF_PHANTOM    // 表示java/lang/ref/PhantomReference类及其子类
};

java.lang.ref.Reference类会用C++InstanceRefKlass的实例表示。当为非常见的引用类型时,_reference_type属性的值为REF_OTHER。通过_reference_type将普通类与引用类型区分开,因为引用类型需要垃圾回收器特殊处理。

(2)InstanceMirrorKlass 

表示java.lang.Class类的InstanceMirrorKlass类实例用于表示特殊的java.lang.Class类,这个类中新增了一个静态属性_offset_of_static_fields,用来保存静态字段的起始偏移量。定义如下: 

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

static int _offset_of_static_fields;

正常情况下,HotSpot VM使用Klass来表示Java类,用oop表示Java对象,而Java类中可能定义静态或非静态字段,非静态字段值存储在oop中,静态字段值存储在表示当前Java类的java.lang.Class对象中。

不过需要特别说明一下java.lang.Class类,这个类比较特殊。java.lang.Class类用InstanceMirrorKlass实例表示,java.lang.Class对象用oop对象表示。由于java.lang.Class类自身也定义了静态字段,这些值同样存储在了java.lang.Class对象中,也就是存储在了表示java.lang.Class对象的oop中,这样静态与非静态字段存储在了一个oop上,需要参考_offset_of_static_fields属性的值进行偏移来定位静态字段的存储位置。

_offset_of_static_fields属性在init_offset_of_static_fields()函数中初始化,如下:

static void init_offset_of_static_fields() {
    // java.lang.Class类使用InstanceMirrorKlass对象来表示,而java.lang.Class对象通过Oop对象来表示,那么imk->size_helper()获取的就是
    // Oop对象的大小,左移3位将字转换为字节。紧要着Oop对象后存储静态字段的值
    InstanceMirrorKlass* imk = InstanceMirrorKlass::cast(SystemDictionary::Class_klass());
    _offset_of_static_fields = imk->size_helper() << LogHeapWordSize; // LogHeapWordSize=3
}

调用size_helper()函数获取oop(表示java.lang.Class对象)的大小,左移3位将字转换为字节。紧接着oop后开始存储静态字段的值。

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

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

int size_helper() const {
    return layout_helper_to_size_helper(layout_helper());
}

调用layout_helper()函数获取Klass类中定义的_layout_helper属性的值,然后调用layout_helper_to_size_helper()函数获取对象所需内存的大小,这个对象占用内存的大小在类解析过程中会计算好并存储到_layout_helper属性中。layout_helper_to_size_helper()函数的实现如下:

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

static int layout_helper_to_size_helper(jint lh) {
    return lh >> LogHeapWordSize;
}

调用size_helper()函数获取oop对象(表示java.lang.Class对象)的大小,这个大小是java.lang.Class类中本身声明的一些实例字段需要占用的内存大小,紧随其后的就是静态存储的区域。

添加虚拟机参数命令-XX:+PrintFieldLayout后,打印的java.lang.Class对象的布局如下:

非静态的布局如下:

java.lang.Class: field layout
  @ 12 --- instance fields start ---
  @ 12 "cachedConstructor" Ljava.lang.reflect.Constructor;
  @ 16 "newInstanceCallerCache" Ljava.lang.Class;
  @ 20 "name" Ljava.lang.String;
  @ 24 "reflectionData" Ljava.lang.ref.SoftReference;  
  @ 28 "genericInfo" Lsun.reflect.generics.repository.ClassRepository;
  @ 32 "enumConstants" [Ljava.lang.Object;
  @ 36 "enumConstantDirectory" Ljava.util.Map;
  @ 40 "annotationData" Ljava.lang.Class$AnnotationData;
  @ 44 "annotationType" Lsun.reflect.annotation.AnnotationType;
  @ 48 "classValueMap" Ljava.lang.ClassValue$ClassValueMap;
  @ 52 "protection_domain" Ljava.lang.Object;
  @ 56 "init_lock" Ljava.lang.Object;
  @ 60 "signers_name" Ljava.lang.Object;
  @ 64 "klass" J
  @ 72 "array_klass" J 
  @ 80 "classRedefinedCount" I
  @ 84 "oop_size" I
  @ 88 "static_oop_field_count" I
  @ 92 --- instance fields end ---
  @ 96 --- instance ends ---

这就是java.lang.Class非静态字段的布局,在类型解析过程中已经计算好了各个字段的偏移量。在完成非静态字段布局后,紧接着会布局静态字段,此时的_offset_of_static_fields字段的值为96。  

我们需要分清相关类的表示方法,如下图所示。

java.lang.Class对象是通过对应的oop实例保存类的静态属性,需要特殊的方式计算他们的大小以及遍历各个属性。

Klass类的_java_mirror属性指向保存该Java类静态字段的oop对象,可通过该属性访问类的静态字段。oopHotSpot VM的对象表示方式,在2.2节将详细介绍。

 (3)InstanceClassLoaderKlass 

表示java.lang.ClassLoader类的此类没有添加新的字段,增加了新的oop遍历方法,在垃圾回收阶段遍历类加载器加载的所有类来标记引用的所有对象。

3、创建类的实例

下面介绍一下HotSpot VM创建Klass类实例的过程。调用InstanceKlass::allocate_instance_klass()函数创建InstanceKlass实例。在创建时首先需要分配内存,这会涉及到C++new运算符重载的调用,通过重载new运算符函数为实例分配内存空间,然后再调用类的构造函数初始化相关的属性。相关函数的实现如下:

 

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

InstanceKlass* InstanceKlass::allocate_instance_klass(
	  ClassLoaderData* loader_data,
	  int vtable_len,
	  int itable_len,
	  int static_field_size,
	  int nonstatic_oop_map_size,
	  ReferenceType rt,
	  AccessFlags access_flags,
	  Symbol* name,
	  Klass* super_klass,
	  bool is_anonymous,
	  TRAPS
) {
  // 获取创建InstanceKlass实例时需要分配的内存大小
  int size = InstanceKlass::size(vtable_len, itable_len, nonstatic_oop_map_size,
                                 access_flags.is_interface(), is_anonymous);

  InstanceKlass* ik;
  if (rt == REF_NONE) {   // 通过InstanceMirrorKlass实例表示java.lang.Class类
    if (name == vmSymbols::java_lang_Class()) {
      ik = new (loader_data, size, THREAD) InstanceMirrorKlass(
        vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
        access_flags, is_anonymous);
  }
  // 通过InstanceClassLoaderKlass实例表示java.lang.ClassLoader或相关子类
  else if (name == vmSymbols::java_lang_ClassLoader() ||
          (SystemDictionary::ClassLoader_klass_loaded() &&
          super_klass != NULL &&
          super_klass->is_subtype_of(SystemDictionary::ClassLoader_klass()))) {
      ik = new (loader_data, size, THREAD) InstanceClassLoaderKlass(
        vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
        access_flags, is_anonymous);
  } 
  // 通过InstanceKlass实例表示普通类
  else {
      // normal class
      ik = new (loader_data, size, THREAD) InstanceKlass(
        vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
        access_flags, is_anonymous);
    }
  } 
  // 通过InstanceRefKlass实例表示引用类型
  else {
    // reference klass
    ik = new (loader_data, size, THREAD) InstanceRefKlass(
        vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
        access_flags, is_anonymous);
  }

  // 添加所有类型到我们内部类加载器列表中,包括在根加载器中的类
  // loader_data的类型为ClassLoaderData*,ClassLoaderData中的_klasses指向通过InstanceKlass._next_link属性连接的单链表
  loader_data->add_class(ik);
  Atomic::inc(&_total_instanceKlass_count);
  return ik;
}

 

首先调用InstanceKlass::size()函数获取表示Java类的Klass实例的大小,这个函数在2.1.2节介绍过,这里不再介绍。然后根据需要创建的类型rt来创建不同的C++类实例。当rtREF_NONE时,普通的Java类通过InstanceKlass实例表示;java.lang.Class类通过InstanceMirrorKlass实例表示;java.lang.ClassLoader类型通过InstanceClassLoaderKlass实例表示。当rt不为REF_NONE时,会创建表示java.lang.Reference类的InstanceRefKlass实例。

通过重载new运算符开辟C++类实例的内存空间,如下:

 

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

void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
// 在元数据区分配内存空间
  return Metaspace::allocate(loader_data, word_size, /*read_only*/false,
                             MetaspaceObj::ClassType, CHECK_NULL);
}

 

对于OpenJDK8版本来说,Klass实例在元数据区分配内存。Klass一般不会卸载,所以没有放到堆中进行管理,堆是垃圾回收器回收的重点,将类的元数据放到堆中时,回收的效率会降低。 

 

posted on 2020-11-21 09:46  鸠摩(马智)  阅读(3253)  评论(0编辑  收藏  举报

导航