JVM基础知识(三)Java对象模型

java对象

在内存中,一个Java对象包含三部分:对象头、实例数据和对齐填充。而对象头中又包含锁状态标志、线程持有的锁等标志。

oop-klass model

OOP(Ordinary Object Pointer)指的是普通对象指针,而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内部使用的特定对象的类型。其中有一个变量oop的类型oopDesc是oops模块的共同基类型。而oopDesc类型又包含instanceOopDesc (类实例)、arrayOopDesc (数组)等子类类型。其中instanceOopDesc 中主要包含以下几部分数据:markOop _mark和union _metadata 以及一些不同类型的 field。

在java程序运行过程中, 每创建一个新的java对象, 在JVM内部就会相应的创建一个对应类型的oop对象来表示该java对象。而在HotSpot虚拟机中, 对象在内存中包含三块区域: 对象头、实例数据和对齐填充。其中对象头包含两部分内容:_mark和_metadata,而实例数据则保存在oopDesc中定义的各种field中。

_mark:

_mark这一部分用于存储对象自身的运行时数据, 如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等, 这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit, 官方称它为 “Mark Word”。对象需要存储的运行时数据很多, 其实已经超出了32位和64位Bitmap结构所能记录的限度, 但是对象头信息是与对象自身定义的数据无关的额外存储成本, 考虑到虚拟机的空间效率, Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息, 它会根据对象的状态复用自己的存储空间。

_metadata:

_metadata这一部分是类型指针, 即对象指向它的类元数据的指针, 虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针, 换句话说查找对象的元数据信息并不一定要经过对象本身, 其取决于虚拟机实现的对象访问方式。目前主流的访问方式有使用句柄和直接指针两种, 两者方式的不同这里先暂不做介绍。另外, 如果对象是一个Java数组, 那么在对象头中还必须有一块用于记录数组长度的数据, 因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小, 但是从数组的元数据中却无法确定数组的大小。

klass

Klass体系:

//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;

和oopDesc是其他oop类型的父类一样,Klass类是其他klass类型的父类。

Klass向JVM提供两个功能:

  • 实现语言层面的Java类(在Klass基类中已经实现)
  • 实现Java对象的分发功能(由Klass的子类提供虚函数实现)

HotSpot JVM的设计者因为不想让每一个对象中都含有一个虚函数表, 所以设计了oop-klass模型, 将对象一分为二, 分为klass和oop。其中oop主要用于表示对象的实例数据, 所以不含有任何虚函数。而klass为了实现虚函数多态, 所以提供了虚函数表。所以,关于Java的多态,其实也有c++虚函数的影子在。

InstanceClass

JVM在运行时,需要一种用来标识Java内部类型的机制。在HotSpot中的解决方案是:为每一个已加载的Java类创建一个InstanceClass对象,用来在JVM层表示Java类。
InstanceClass内部结构:

//类拥有的方法列表
objArrayOop     _methods;
//描述方法顺序
typeArrayOop    _method_ordering;
//实现的接口
objArrayOop     _local_interfaces;
//继承的接口
objArrayOop     _transitive_interfaces;
//域
typeArrayOop    _fields;
//常量
constantPoolOop _constants;
//类加载器
oop             _class_loader;
//protected域
oop             _protection_domain;
    ....

在JVM中,对象在内存中的基本存在形式就是oop。那么,对象所属的类,在JVM中也是一种对象,因此它们实际上也会被组织成一种oop,即klassOop。同样的,对于klassOop,也有对应的一个klass来描述,它就是klassKlass,也是klass的一个子类。klassKlass作为oop的klass链的端点, 它的klass就是它自身。

内存存储

我们首先来看看下面这段代码的存储结构。

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);
}

存储结构如下:
在这里插入图片描述
由此我们能得出结论: 对象的实例(instantOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。

小结

在JVM加载java类的时候, JVM会给这个类创建一个instanceKlass并保存在方法区, 用来在JVM层表示该java类。当我们使用new关键字创建一个对象时, JVM会创建一个instanceOopDesc对象, 这个对象包含了对象头和元数据两部分信息。对象头中有一些运行时数据, 其中就包括和多线程有关的锁的信息。而元数据维护的则是指向对象所属的类的InstanceKlass的指针。

参考资料

深入理解多线程(二)—— Java的对象模型-HollisChuang’s Blog

深入理解多线程(三)—— Java的对象头-HollisChuang’s Blog

<<深入理解Java虚拟机>>

猜你喜欢

转载自blog.csdn.net/weixin_37910453/article/details/88050470
今日推荐