java对象保存在堆内存中。在内存中,一个Java对象包含三部分:对象头、实例数据和对象填充。
对象头中包含锁状态标志、线程持有的锁等标志。
在JVM的内存结构中,对象保存在堆内存中。我们在对对象进行操作时,操作的是对象的引用。
那么对象本身在JVM中的结构是什么样的?
HotSpot JVM的设计者不想让每个对象中都含有一个vtable(虚函数表)(HotSpot基于C++实现),所以设计了一个OOP-Klass Model来表示对象。OOP(Ordinary Object Pointer)指的是普通对象指针,Klass用来描述对象实例的具体类型。
C++在运行时不维护类型信息,所以在编译时直接在子类的虚函数表中将被子类重写的方法替换掉。
Java在运行时会维护类型信息和类的继承体系,每一个类在方法区中会对应一个数据结构用于存放类的信息,可以通过Class对象访问这个数据结构。而每一个在堆上创建的对象,都具有一个指向方法区类型信息数据结构的指针,通过这个指针可以确定对象的类型。
oop-klass结构:
oop体系:
//定义了oops共同基类 typedef class oopDesc* oop; //表示一个Java类型实例 typedef class instanceOopDesc* instanceOop; //表示一个Java方法 typedef class methodOopDesc* methodOop; //表示一个Java方法中的不变信息 typedef class constMethodOopDesc* constMethodOop; //记录性能信息的数据结构 typedef class methodDataOopDesc* methodDataOop; //定义了数组OOPS的抽象基类 typedef class arrayOopDesc* arrayOop; //表示持有一个OOPS数组 typedef class objArrayOopDesc* objArrayOop; //表示容纳基本类型的数组 typedef class typeArrayOopDesc* typeArrayOop; //表示在Class文件中描述的常量池 typedef class constantPoolOopDesc* constantPoolOop; //常量池告诉缓存 typedef class constantPoolCacheOopDesc* constantPoolCacheOop; //描述一个与Java类对等的C++类 typedef class klassOopDesc* klassOop; //表示对象头 typedef class markOopDesc* markOop;
上面是整个oops模块的组成结构,其中包括多个子模块,每个子模块对应一个类型,每个类型的oop都代表一个在JVM内部使用的特定对象的类型。
OOPS类的共同基类是oopDesc。
这些OOPS在JVM内部有者不同的用途,例如instanceOopDesc表示类实例,arrayOopDesc表示数组。
也就是说,当我们用new创建一个Java对象实例的时候,JVM会创建一个instanceOopDesc对象来表示这个Java对象。同理,对于数组实例则是arrayOopDesc。
oopDesc类的定义:
class oopDesc { friend class VMStructs; private: volatile markOop _mark; union _metadata { wideKlassOop _klass;//普通指针 narrowOop _compressed_klass;//压缩类指针 } _metadata; private: // field addresses in oop void* field_base(int offset) const; jbyte* byte_field_addr(int offset) const; jchar* char_field_addr(int offset) const; jboolean* bool_field_addr(int offset) const; jint* int_field_addr(int offset) const; jshort* short_field_addr(int offset) const; jlong* long_field_addr(int offset) const; jfloat* float_field_addr(int offset) const; jdouble* double_field_addr(int offset) const; address* address_field_addr(int offset) const; } class instanceOopDesc : public oopDesc { } class arrayOopDesc : public oopDesc { }
instanceOopDesc主要包含 markOop _mark
和 union _metadata
,示例数据则保存在oopDesc中定义的各种field中。
_mark保存了锁标志、GC分代等信息。
//klassOop的一部分,用来描述语言层的类型 class Klass; //在虚拟机层面描述一个Java类 class instanceKlass; //专有instantKlass,表示java.lang.Class的Klass class instanceMirrorKlass; //专有instantKlass,表示java.lang.ref.Reference的子类的Klass class instanceRefKlass; //表示methodOop的Klass class methodKlass; //表示constMethodOop的Klass class constMethodKlass; //表示methodDataOop的Klass class methodDataKlass; //最为klass链的端点,klassKlass的Klass就是它自身 class klassKlass; //表示instanceKlass的Klass class instanceKlassKlass; //表示arrayKlass的Klass class arrayKlassKlass; //表示objArrayKlass的Klass class objArrayKlassKlass; //表示typeArrayKlass的Klass class typeArrayKlassKlass; //表示array类型的抽象基类 class arrayKlass; //表示objArrayOop的Klass class objArrayKlass; //表示typeArrayOop的Klass class typeArrayKlass; //表示constantPoolOop的Klass class constantPoolKlass; //表示constantPoolCacheOop的Klass class constantPoolCacheKlass;
Klass类是其他klass类型的父类。
Klass向JVM提供两个功能:
HotSpot JVM的设计者把对象拆为klass和oop,其中oop的职能主要在于表示对象的示例数据,其中不包含虚函数。而klass为了实现虚函数多态,提供了虚函数表。_metadata是一个共用体,其中_klass是普通指针,_compressed_klass是压缩类指针,这两个指针都指向instanceKlass对象,它用来描述对象的具体类型。
JVM在运行时,需要一种用来标识Java内部类型的机制。HotSpot的解决方案是,为每一个已加载的Java类创建一个instanceKlass对象,用来在JVM层表示Java类。
//类拥有的方法列表 objArrayOop _methods; //描述方法顺序 typeArrayOop _method_ordering; //实现的接口 objArrayOop _local_interfaces; //继承的接口 objArrayOop _transitive_interfaces; //域 typeArrayOop _fields; //常量 constantPoolOop _constants; //类加载器 oop _class_loader; //protected域 oop _protection_domain; ....
在JVM中,对象在内存中的基本存在形式就是oop,对象所属的类也是一种对象,即klassOop。对于klassOop也有一个对应的klass来描述。就是klassKlass。
在这种设计下,JVM对内存的分配和回收,都可以采用统一的方式来管理。
对象的实例(instanceOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。 方法区用于存储虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
class Model { public static int a = 1; public int b; public Model(int b) { this.b = b; } } public static void main(String[] args) { int c = 10; Model modelA = new Model(2); Model modelB = new Model(3); }
每一个java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了两部分信息,对象头和元数据。对象头有一些运行时的数据,其中就包括跟多线程相关的锁的信息。元数据维护的是指针,指向的是对象所属的类的instanceKlass。