Java的对象模型——Oop-Klass模型(一)

前言

谈起Java对象,笔者的第一反应是在:Java中的每一个对象(不包括基础类型)都继承于Object对象。相信这也是大多数程序员对Java对象的初次印象,Object可以表示所有的Java对象。但是,这种理解仅仅是停留在语言层面,至于更深的JVM层面,对象还是用Object来表示吗?显然不是。JVM通常使用非Java语言实现,是用来解析并运行Java程序的,它有自己的模型来表示Java语言的各种特性,包括Object。下面我们以HotSpot为例,一起来探讨Java对象在JVM层面的Java对象模型。

HotSpot采用C++语言实现,下文中的JVM如无特殊说明,指的都是HotSpot。

Java程序通过new操作符来创建一个对象,在深入探讨HotSpot的Java对象模型前,我们先看下new操作符的具体实现。

上述代码片段来自HotSpot源码中new操作符的实现函数,先不深入分析每一行的具体含义,这段代码给我们最直观的功能就是:先对klass对象进行初始化工作,然后再用它来创建出oop对象。到这里我们大致就能猜出,oop表示的就是一个Java对象。而这里的klass和Java中的Class之间似乎有着紧密的联系,一是两者的名字非常类似,另外也可通过第16行代码得到进一步的肯定。对Java的反射机制稍微有所了解的人,看着第16行代码一定很熟悉,因为它与使用Class.newInstance()方法来创建Object对象很类似。

实际正如上述所猜测,HotSpot使用Oop-Klass模型来表示Java的对象。

Oop的继承体系

这里的Oop并非是Object-oriented programming,而是Ordinary object pointer(普通对象指针),是HotSpot用来表示Java对象的实例信息的一个体系。其中oop是Oop体系中的最高父类,整个继承体系如下所示:

Oop继承体系
Oop继承体系

每个Java对象都有它独有的生命周期,我们使用new操作符将它创建出来,然后对它执行各式各样的操作(如获取成员属性、调用成员函数、加锁等),最后被GC回收掉。那么Java对象的这一系列经历,JVM又是怎么实现的呢?JVM使用Oop来表示一个Java对象,自然地,这些经历都会跟oop有关。

oop的子类有两个,分别是instanceOoparrayOop。前者表示Java中普通的对象,后者则表示数组对象。arrayOop也有两个子类,objArrayOop表示普通对象类型的数组,而typeArrayOopDesc则表示基础类型的数组。如下图所示,oop的存储结构主要由对象头和对象体组成。

Java对象的存储结构
Java对象的存储结构

oop对象头

oop主要有两个成员属性:

_mark_metadata被称为对象头,其中前者存储对象的运行时记录信息;后者是一个指针,指向当前对象所属的Klass对象。

因为某些历史原因,HotSpot把markOop放到Oop体系里,但是它并继承oop,因此前文所描述的Oop体系并没有包含它。

markOop的存储结构在32位和64位系统中有所差异,但是具体存储的信息是一样的,本节只介绍它在32位系统中的存储结构。在32位系统中,markOop一共占32位,存储结构如图下所示:

markOop存储结构
markOop存储结构

从图中可知,诸如对象hash值、线程ID、分代年龄等信息都是存储在markOop中,而且在不同的状态下,其结构也是略有不同。无锁指一个对象没有被加锁时的状态;偏向锁,顾名思义会偏向于第一个访问锁的线程,当同步锁只有一个线程访问时,JVM会将其优化为偏向锁,此时就相当于没有同步语义;当发生多线程竞争时,偏向锁就会膨胀为轻量级锁,后者采用CAS(Compare And Swap)实现,避免了用户态和内核态之间的切换;如果某个线程获取轻量级锁失败,该锁就会继续膨胀为重量级锁,此时JVM会向操作系统申请互斥量,因此性能消耗也是最高的。

oop提供4个方法来判断当前对象处于何种状态下:

从上述代码可知,oop调用markOop的方法来判断当前对象是否已经加锁、是否是偏向锁,markOop则通过判断其存储结构中的标志位来实现,如下列代码所示:

oop对象体

JVM将Java对象的field存储在oop对象体中,oop提供了一系列的方法来获取和设置field,并且针对每种基础类型都提供了特有的实现。

具体实现如下列代码所示:

由上述代码片段可知,每个field在oop中都有一个对应的偏移量(offset),oop通过该偏移量得到该field的地址,再根据地址得到具体数据。因此,Java对象中的field存储的并不是对象本身,而是对象的地址

图1-11 Java对象field的存储结构
图1-11 Java对象field的存储结构

总结

HotSpot采用Oop-Klass模型来表示Java对象,其中Klass对应着Java对象的类型(Class),而Oop则对应着Java对象的实例(Instance)。Oop是一个继承体系,其中oop是体系中的最高父类,它的存储结构可以分成对象头和对象体。对象头存储的是对象的一些元数据,对象体存储的是具体的成员属性。值得注意的是,如果成员属性属于普通对象类型,则oop只存储它的地址

我们都知道Java中的普通方法(没有static和final修饰)是动态绑定的,在C++中,动态绑定通过虚函数来实现,代价是每个C++对象都必须维护一张虚函数表。Java的特点就是一切皆是对象,如果每个对象都维护一张虚函数表,内存开销将会非常大。JVM对此做了优化,虚函数表不再由每个对象维护,改成由Class类型维护,所有属于该类型的对象共用一张虚函数表。因此我们并没有在oop上找到方法调用的相关逻辑,这部分的代码被放在了klass里面。

Klass相关的内容将会在下一篇文章《Java的对象模型——Oop-Klass(二)》中介绍。

https://juejin.im/post/5e33dd846fb9a02ffe703676

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论