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的继承体系如下:
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;
}
}
复制代码
- 首先NarrowPtrStruct结构体中_shift字段,从下面第一行代码中可以看出,这个值在默认初始化是0。
- 计算对象的指针地址与narrow_klass_base压缩指针的基地址的差值,除以1后右移shift位,校验越界, 校验decode后的值是否等于encode前的值。
- 最后返回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提供两个功能:
- 语言层面的类对象(比如: method、dictionary对象)。
- 为对象提供虚拟机的方法分发行为
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的类层级结构
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的更加深入的内容是会很大帮助.