只要是对JVM有所了解的,或多或少的都知道oop-klass模型.那么什么是oop-klass模型?
JVM内部基于oop-klass模型描述一个java类,将一个java类分为两个部分进行描述,其中第一个模型是oop,第二个模型是klass.
接下来,本文从以下几个角度进行讲解:
JVM使用oop-klass这种模型来描述一个java类.虽然模型有2个,但是确实从3个不同的角度去看待java类的:
那么这里有一个知识点: 什么是vtable,itable呢?
简要概括就是:
vtable: 该类所有的函数(除了static, final)和 父类的函数虚拟表。
Itable: 该类所有实现接口的函数列表.
JVM中使用了oop,klass,handle 这三个类来描述oop-klass.需要注意一点的是: 这三个类不仅能够描述外在的java类,也能描述JVM内在的类型(如 constantPoolOop).
oop,klass之前有介绍了,那么handle是啥意思?
handle 是对oop的行为的封装.这里需要注意的是:
其中的关系如下:
oop 就是 ordinary object pointer,也即普通对象指针.oop成员众多,其类图如下:
这里有个问题,不是说xxxoop吗?怎么类图上怎么就是xxxppDesc了?
原因在于: oopsHierarchy.hpp 中进行了如下定义:
typedef class oopDesc* oop;
typedef class instanceOopDesc* instanceOop;
typedef class methodOopDesc* methodOop;
typedef class constMethodOopDesc* constMethodOop;
typedef class methodDataOopDesc* methodDataOop;
typedef class arrayOopDesc* arrayOop;
typedef class objArrayOopDesc* objArrayOop;
typedef class typeArrayOopDesc* typeArrayOop;
typedef class constantPoolOopDesc* constantPoolOop;
typedef class constantPoolCacheOopDesc* constantPoolCacheOop;
typedef class symbolOopDesc* symbolOop;
typedef class klassOopDesc* klassOop;
typedef class markOopDesc* markOop;
typedef class compiledICHolderOopDesc* compiledICHolderOop;
其中各类的说明如下:
类名 | 说明 |
oop | oop的顶级父类 |
instanceOop | 表示java类实例 |
methodOop | 表示java方法 |
constMethodOop | 表示java方法中的只读信息(其实就是字节码指令) |
methodDataOop | 表示性能统计的相关数据 |
arrayOop | 数组对象,定义了数组oops的抽象基类 |
objArrayOop | 表示引用类型数组对象 |
typeArrayOop | 表示基本类型数组对象 |
constantPoolOop | 表示java字节码文件中的常量池 |
constantPoolCacheOop | 与constantPoolOop 相伴生,是后者的缓存对象,缓存了字段和方法的访问信息,为允许时环境快速访问字段和方法提供重要作用 |
symbolOop | symbolOop是规范化字符串。所有symbolOop都位于全局符号表中。 |
klassOop | 指向jvm内部的klass实例的对象 |
markOop | oop 的标记对象,表示对象头 |
compiledICHolderOop | 内联缓存实现的帮助器对象。它包含从编译到解释调用转换时使用的中间值(method+klass对),总是分配在永久区(以避免在清除期间遍历codecache) |
klass: 按照官方解释,klass 主要提供下面2种能力:
其类图如下:
handle 部分的类图如下:
通过对比三者可以发现: handle 分别给oop和klass定义了不同的体系?这与我们之前给handle的定义有出入–> handle 是对oop的行为的封装.
那么这部分的原因很简单: 在jvm中,使用oop-klass这种一分为二的模型去描述java类以及JVM内部的特殊类,为此JVM内部定义了各种oop和klass类型.但是,对于每一个oop,其实都是一个C++类型,也即klass;而对于每一个klass所对应的class,在JVM内部又会被封装成oop.
JVM在加载类时,会使用oop去存储这个类型的实例数据,并使用klass去存储这个类型的元数据和虚方法表.而当一个类型完成其生命周期后,JVM会通过GC去回收,在回收时,既要回收一个类实例所对应的实例数据oop,也要回收其所对应的元数据和方法分发表(当然,二者的回收时机是不同的).为了让GC即能回收oop也能回收klass,因此oop本身被封装成oop,klass也被封装成oop.
handle与oop关联:
在handle继承体系中, handle这个顶层类中,定义了如下字段:
oop* _handle;
而该字段会在实例化的时候,会通过构造器进行赋值:
Handle::Handle(Thread* thread, oop obj) {
assert(thread == Thread::current(), "sanity check");
if (obj == NULL) {
_handle = NULL;
} else {
_handle = thread->handle_area()->allocate_handle(obj);
}
}
inline Handle::Handle(oop obj) {
if (obj == NULL) {
_handle = NULL;
} else {
_handle = Thread::current()->handle_area()->allocate_handle(obj);
}
}
oop与klass进行关联:
oopDesc做为oop体系的顶层类,其中定义了如下字段:
union _metadata {
wideKlassOop _klass;
narrowOop _compressed_klass;
} _metadata;
而wideKlassOop类型为:klassOopDesc*
narrowOop类型为:uint32_t(jdk 1.6)
当开启了指针压缩时,会使用narrowOop,反之,使用wideKlassOop 来指向klassOopDesc.
该字段会通过oopDesc::set_klass(klassOop k) 来进行设置,如下:
inline void oopDesc::set_klass(klassOop k) {
// since klasses are promoted no store check is needed
assert(Universe::is_bootstrapping() || k != NULL, "must be a real klassOop");
assert(Universe::is_bootstrapping() || k->is_klass(), "not a klassOop");
if (UseCompressedOops) {
oop_store_without_check(compressed_klass_addr(), (oop)k);
} else {
oop_store_without_check(klass_addr(), (oop) k);
}
}