JVM中的oop-klass二分模型

JVM是由C++和C混合开发,C++中类是定义模板,实际对象是按照class分配内存空间产生的class的对象实例,内存是没有class自身对象,这也解释了为什么C++没有反射能力的原因,当然java为什么要设计oop-klass模型?,下面引用jdk1.8的源码中注释

One reason for the oop/klass dichotomy in the implementation is
that we don’t want a C++ vtbl pointer in every object. Thus,
normal oops don’t have any virtual functions. Instead, they
forward all “virtual” functions to their klass, which does have
a vtbl and does the C++ dispatch depending on the object’s
actual type.

oop/klass二分实是因为我们不想要在每一个类的实例对象中有一个C++的vtbl指针,因此普通对象并没有任何虚拟方法,反而,他们将所有的虚方法转移到做由vtbl指针和依赖对象实际类型做C++分发的klass中。

OOP

下面是引用自jdk1.8中oop.hpp中注释,来解释oop是什么?首先明确下它不是Object Oriented Programming,实际是普通对象指针(ordinary object point)。
oopDesc是class对象的最顶层的基类,以Desc为结尾的类描述了java对象的格式,所以这个属性字段可以通过C++访问,oopDesc是抽象的,不允许使用虚方法.

oopDesc is the top baseclass for objects classes. The {name}Desc classes describe
the format of Java objects so the fields can be accessed from C++.
oopDesc is abstract.
no virtual functions allowed

java对象格式

class oopDesc {
     volatile markOop  _mark;
     union _metadata {
     Klass*      _klass;
     narrowKlass _compressed_klass;
    } _metadata;
 }
复制代码

可以看出java对象头中具由有一个markwOpp对象和_metadata的共用体对象。\

markOoo对象头

从下面宏定义可以知道markOop实际是markOopDesc的指针的别名.所以oopDesc作为java所有实例对象的对象头中_mark字段实际是markOopDesc类型的指针.

 typedef    class      markOopDesc*            markOop;
复制代码

oopDesc的继承体系如下:
image.png
markOop描述了一个java对象的头部,它只是描述一个对象的一个机器的字长(word),也称的对象头的markword,它位于oop继承体系的最顶部的位置。
32位机器二进制位的格式如下:

//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
复制代码

64位的二进制如下:

/  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
复制代码

偏向锁中对象头的markword中biased_lock一位是1,lock的2位是01.

//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
复制代码

lock两位的锁描述的锁状态如下

//    [ptr             | 00]  locked             轻量级锁
//    [header      | 0 | 01]  unlocked           无锁
//    [ptr             | 10]  monitor            重量级锁
//    [ptr             | 11]  marked             标识用于GC
//                                             
复制代码

_metadata 元数据指针

_metadata就是保存的是只想klass的指针,之所以使用共用体结构是为了在64位机器上使用指针压缩,如果JVM的UseCompressedClassPointers参数是true,则使用narrowKlass类型,实际就是int类型4个字节,这样就可以减少内存的占用。

typedef unsigned int            uintptr_t;
// If compressed klass pointers then use narrowKlass.
typedef juint  narrowKlass;
复制代码

如果开启使用指针压缩,在java分配对象时,set_klass设则时候会调用Klass::encode_klass_not_null方法去编码klass压缩klass指针压缩.

inline void oopDesc::set_klass(Klass* k) {
  // since klasses are promoted no store check is needed
  assert(Universe::is_bootstrapping() || k != NULL, "must be a real Klass*");
  assert(Universe::is_bootstrapping() || k->is_klass(), "not a Klass*");
  if (UseCompressedClassPointers) {
    *compressed_klass_addr() = Klass::encode_klass_not_null(k);
  } else {
    *klass_addr() = k;
  }
}
复制代码
  1. 首先NarrowPtrStruct结构体中_shift字段,从下面第一行代码中可以看出,这个值在默认初始化是0。
  2. 计算对象的指针地址与narrow_klass_base压缩指针的基地址的差值,除以1后右移shift位,校验越界, 校验decode后的值是否等于encode前的值。
  3. 最后返回encode后的narrowKlass类型指针。
NarrowPtrStruct Universe::_narrow_oop = { NULL, 0, true };

struct NarrowPtrStruct {
  address _base;
  int     _shift;
  bool    _use_implicit_null_checks;
};
inline narrowKlass Klass::encode_klass_not_null(Klass* v) {
  int   shift = Universe::narrow_klass_shift();
  uint64_t pd = (uint64_t)(pointer_delta((void*)v,   Universe::narrow_klass_base(), 1));
  uint64_t result = pd >> shift;
  assert((result & CONST64(0xffffffff00000000)) == 0, "narrow klass pointer overflow");
  assert(decode_klass(result) == v, "reversibility");
  return (narrowKlass)result;
}
复制代码

这里有一个有细节: shift的取值可以0或3,也就encode后的klass指针地址,由于右移shift位后,那么klass的指针可以2的32位最大表示4G,然后右移3位,所以指针地址最多可以表示32G的地址.

klass

下面是引用klass.hpp中注释。
klass提供两个功能:

  1. 语言层面的类对象(比如: method、dictionary对象)。
  2. 为对象提供虚拟机的方法分发行为

A Klass provides:
1: language level class object (method dictionary etc.)
2: provide vm dispatch behavior for the object
Both functions are combined into one C++ class

klass的类层级结构
image.png

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] \

总结
本文主要是分析JVM的oop-klass二分模型的产生原因以及它们在JVM中各自有用的属性以及作用,有了这个基础后,对于了解JVM的更加深入的内容是会很大帮助.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享