让咱们思考这样一个问题:一个Java对象如何在基于c++实现的系统中运行?对象在JVM内部是如何表示的?它在内存中是如何存储的......java
1.OOP-Klass 二分模型c++
Java是面向对象的语言,面向对象有三个特征:封装、继承和多态。而HotSpot基于C++实现,C++也是面向对象的语言,那这样的话为每个Java类生成一个C++类不就OK了吗?事实并非这样,对象在JVM内的表示被设计成了一种新的表示方式:OOP-Klass 二分模型:数组
■ OOP:或OOPS,即普通对象指针,用来描述对象实例信息。多线程
■ Klass :Java类的C++对等体,用来描述对象实例的具体类型。函数
为何要设计这样的模型呢?缘由:HotSpot JVM的设计者不想让每个对象中都含有一个vtable(虚函数表)。oop
2.OOP-Klass模块:布局
模块说明以下:spa
OOP线程
上面列出的是整个Oops模块的体系结构,其中包含多个子模块,每一个子模块对应一个类型,每个类型的OOP都表明一个在JVM内部使用的特定对象类型。在Java应用程序运行过程当中,每建立一个Java类对象,在JVM内部也会相应的建立一个OOP对象来表示Java对象。OOPS类的共同基类型为oopDesc。根据JVM内部使用的对象业务类型,具备多种oopDesc子类,每种类型的OOP都表明一个在JVM内部使用的特定对象类型。设计
这些OOPS类在JVM内部有着不一样的用途:
例如:instanceOopDesc表示类型实例,arrayOopDesc表示数组。也就是说,当咱们使用new建立一个Java对象实例的时候,JVM会建立一个instanceOopDesc对象来表示这个Java对象;同理,当咱们使用new建立一个Java数组实例的时候,JVM会建立一个arrayOopDes对象来表示这个数组对象。
在JVM内部,经过instanceOopDesc来表示一个Java对象。对象在内存中的布局能够分为连续的两部分:instanceOopDesc和实例数据。其中,instanceOopDesc或arrayOopDesc又被称为对象头,对象头包含两部分信息:Mark Word(_mark)和元数据指针(_metadata)。其中,Mark Word存储对象运行时记录信息,入哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线性ID等;元数据指针指向描述类型的Klass对象的指针,Klass对象包含了实例对象所属类型的元数据,虚拟机运行时将频繁使用到这个指针定位到位于方法区内的类型信息。
Klass
和OopDesc是其余Oop类型的父类同样,Klass是其余klass类型的父类。如图:
Klass向JVM提供两个功能:
■ 语言层面实现Java类(在Klass基类中已实现)
■ 实现Java对象的分发功能(由Klass的子类提供虚函数实现)
HotSpot JVM的设计者把对象一分为二,分为klass和oop,其中oop的职能主要在于表示对象的实例数据,因此其中不含任何虚函数;而klass为了实现虚函数多态,因此提供了虚函数表。_metadata是一个共用体,其中_klass是普通指针,_compressed_klass是压缩类指针。这两个指针都指向instanceKlass对象,它用来描述对象的具体类型。
JVM在运行时,须要一种用来标识Java内部类型的机制。在HotSpot中的解决方案是:为每个已加载的Java类建立一个instanceKlass对象,用来在JVM层表示Java类。
instanceKlass的内部机构:
在JVM中,对象在内存中的基本存在形式就是oop。那么,对象所属的类,在JVM中也是一种对象,所以他们实际上也会被组织成一种oop,即klassOop。一样的,对于klassOop,也有对应的一个klass来描述,就是klassKlass,也是klass的一个子类。klassKlass做为oop的klass链的端点,以下图:
在这种设计下,JVM对内存的分配和回收,均可以采用同一的方式来管理。oop通常能够理解为咱们常说的对象,klassOop可理解为Java类在JVM中的表示,klassKlass描述了如何表示一个类。为了更形象的表示一个java类在内存中的存储结构,请看下图:
总结:
每个Java类,在被JVM加载的时候,JVM会给这个类建立一个instanceklass,保存在方法区,用来在JVM层表示该Java类。当咱们在Java代码中,使用new建立一个对象时,JVM会建立一个instanceOopDesc对象,此对象包含了两部分信息:对象头和元数据。对象头中有一些运行时数据,其中就包括和多线程相关的锁信息;元数据维护的是指针,指向的是对象所属的类的instaceKlass。