JAVA对象OOP-Klass模型

"welcome to ARTAvrilLavigne Blog"

Posted by ARTAvrilLavigne on June 15, 2020

一、HotSpot的对象模型

  HotSpot中采用了OOP-Klass模型,它是用来描述Java对象实例的一种模型,OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。那么为何要设计这样一个一分为二的对象模型呢?这是因为HotSopt JVM的设计者不想让每个对象中都含有一个vtable(虚函数表),所以就把对象模型拆成klass和oop,其中oop中不含有任何虚函数,而klass就含有虚函数表,可以进行method dispatch。这个模型其实是参照Strongtalk VM底层的对象模型。
  HotSpot中,用instanceOopDesc和arrayOopDesc来描述对象头,其中arrayOopDesc对象用于描述数组类型。其中instanceOopDesc的代码如下所示(openjdk/hotspot/src/share/vm/oops/instanceOop.hpp):

class instanceOopDesc : public oopDesc {
 public:
  // aligned header size.
  static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }

  // If compressed, the offset of the fields of the instance may not be aligned.
  static int base_offset_in_bytes() {
    // offset computation code breaks if UseCompressedClassPointers
    // only is true
    return (UseCompressedOops && UseCompressedClassPointers) ?
             klass_gap_offset_in_bytes() :
             sizeof(instanceOopDesc);
  }

  static bool contains_field_offset(int offset, int nonstatic_field_size) {
    int base_in_bytes = base_offset_in_bytes();
    return (offset >= base_in_bytes &&
            (offset-base_in_bytes) < nonstatic_field_size * heapOopSize);
  }
};

  其中instanceOopDesc继承自oopDesc(openjdk/hotspot/src/share/vm/oops/oop.hpp)代码如下所示:oopDesc中包含两个数据成员:_mark 和 _metadata。其中markOop类型的_mark对象指的是对象头中的Mark World。_metadata是一个共用体,其中_klass是普通指针,_compressed_klass是压缩类指针,它们就是元数据指针,这两个指针都指向instanceKlass对象,用来描述对象的具体类型。

class oopDesc {
    friend class VMStructs;
 private:
    // 对象头(Mark Word和Klass*)
    volatile markOop  _mark;
    union _metadata {
        Klass*      _klass;
        narrowKlass _compressed_klass;
    } _metadata;

    // Fast access to barrier set.  Must be initialized.
    static BarrierSet* _bs;
    ...
}

二、oop和klass结构定义

  oop的结构定义如下所示:

typedef class     oopDesc*                    oop;
typedef class     instanceOopDesc*            instanceOop;
typedef class     arrayOopDesc*               arrayOop;
typedef class     objArrayOopDesc*            objArrayOop;
typedef class     typeArrayOopDesc*           typeArrayOop;

  由于Java8引入了Metaspace,OpenJDK 1.8里对象模型的实现与1.7有很大的不同。原先存于PermGen的数据都移至Metaspace,因此它们的C++类型都继承于 MetaspaceObj 类(定义见vm/memory/allocation.hpp ),表示元空间的数据。
元数据的结构定义如下所示:

// The metadata hierarchy is separate from the oop hierarchy

//      class MetaspaceObj
class   ConstMethod;
class   ConstantPoolCache;
class   MethodData;
//      class Metadata
class   Method;
class   ConstantPool;
//      class CHeapObj
class   CompiledICHolder;

  klass的结构定义如下所示:

// The klass hierarchy is separate from the oop hierarchy.

class     Klass;
class     InstanceKlass;
class     InstanceMirrorKlass;
class     InstanceClassLoaderKlass;
class     InstanceRefKlass;
class     ArrayKlass;
class     ObjArrayKlass;
class     TypeArrayKlass;

  注意klass代表元数据,继承自Metadata类,因此像Method、ConstantPool 都会以成员变量(或指针)的形式存在于klass体系中。

  以下是JDK 1.7中的类在JDK 1.8中的存在形式:

klassOop -> Klass*
klassKlass 不再需要
methodOop -> Method*
methodDataOop -> MethodData*
constMethodOop -> ConstMethod*
constantPoolOop -> ConstantPool*
constantPoolCacheOop -> ConstantPoolCache*

三、klass介绍

  一个klass对象代表一个类的元数据(相当于java.lang.Class对象)。它提供:
  1、language level class object (method dictionary etc.)
  2、provide vm dispatch behavior for the object.
  所有的函数都被整合到一个C++类中。klass对象的继承关系: xxxKlass 继承于=> Klass 继承于=> Metadata 继承于=> MetaspaceObj
  klass对象的布局如下:

//    Klass layout:
//    [C++ vtbl ptr  ] (contained in Metadata)
//    [layout_helper ]
//    [super_check_offset   ] for fast subtype checks
//    [name          ]
//    [secondary_super_cache] for fast subtype checks
//    [secondary_supers     ] array of 2ndary supertypes
//    [primary_supers 0]
//    [primary_supers 1]
//    [primary_supers 2]
//    ...
//    [primary_supers 7]
//    [java_mirror   ]
//    [super         ]
//    [subklass      ] first subclass
//    [next_sibling  ] link to chain additional subklasses
//    [next_link     ]
//    [class_loader_data]
//    [modifier_flags]
//    [access_flags  ]
//    [last_biased_lock_bulk_revocation_time] (64 bits)
//    [prototype_header]
//    [biased_lock_revocation_count]
//    [_modified_oops]
//    [_accumulated_modified_oops]
//    [trace_id]

四、oop介绍

  oop类型其实是oopDesc*。在Java程序运行的过程中,每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的oop对象。各种oop类的共同基类为oopDesc类。JVM内部,一个Java对象在内存中的布局可以连续分成两部分:instanceOopDesc实例数据instanceOopDescarrayOopDesc又称为对象头。
  instanceOopDesc对象头包含两部分信息:Mark Word元数据指针(klass*)

volatile markOop  _mark;
  union _metadata {
    Klass*        _klass;
    narrowKlass   _compressed_klass;
  } _metadata;

  分别来看一下:
  1、Mark Word:instanceOopDesc中的 _mark 成员。它用于存储对象的运行时记录信息,如哈希值、GC分代年龄(Age)、锁状态标志(偏向锁、轻量级锁、重量级锁)、线程持有的锁、偏向线程ID、偏向时间戳等,并且Mark Word允许压缩。
  2、元数据指针:instanceOopDesc中的 _metadata 成员,它是联合体,可以表示未压缩的Klass指针( _klass )和压缩的Klass指针。对应的klass指针指向一个存储类的元数据的Klass对象。

五、总结

  最后分析一波,执行new A()的时候,JVM里发生了什么。首先,如果这个类没有被加载过,JVM就会进行类的加载,并在元空间创建一个instanceKlass对象表示这个类的运行时元数据。到初始化的时候(执行 invokespecial A::<init> ),JVM就会创建一个instanceOopDesc对象表示这个对象的实例,然后进行Mark Word的填充,将元数据指针指向Klass对象,并填充实例变量。即元数据——instanceKlass对象会存在元空间,而对象实例——instanceOopDesc会存在Java堆,Java虚拟机栈中会存有这个对象实例的引用。故根据OOP-Klass模型,我们就可以分析Java虚拟机是如何通过栈帧中的对象引用找到对应的对象实例,如下图所示。从图中可以看出,通过栈帧中的对象引用找到Java堆中的instanceOopDesc对象,再通过instanceOopDesc中的元数据指针来找到方法区中的instanceKlass,从而确定该对象的具体类型。

object

六、参考

1、https://www.tuicool.com/articles/ryQv2iB
2、https://www.cnblogs.com/ganchuanpu/p/6217342.html
3、https://blog.csdn.net/CodeFarmer__/article/details/102491986


みなさんのごおうえんをおねがいします~~