oop-klass模型

Hotspot 虚拟机在内部使用两组类来表示Java的对象和类。

  • oop(ordinary  object  pointer),用来描述对象实例信息。
  • klass,用来描述 Java 类,是虚拟机内部Java类型结构的对等体 。

JVM内部定义了各种oop-klass,在JVM看来,不仅Java类是对象,Java 方法也是对象, 字节码常量池也是对象,一切皆是对象。JVM使用不同的oop-klass模型来表示各种不同的对象。 而在技术落地时,这些不同的模型就使用不同的 oop 类(instanceoop  methodoop constmethodoop等等)和 klass 类来表示 。由于JVM使用C/C++编写,因此这些 oop 和 klass 类便是各种不同的C++类。对于Java类型与实例对象,只叫使用 instanceOop 和 instanceKlass 这 2 个 C++类来表示。

描述HotSpot中的oop 体系

也许是为了简化变量名,JVM统一将最后的Desc去掉,全部处理成以 Oop 结尾的类型名。 例如对于 Java 类中所定义的方法 ,只明使用 methodOop 去描述 Java 方法的全部信息;对于 Java 类中所定义的引用对象变量 ,JVM则使用objArrayOop来保存这个引用变量的 “全息”信息。


纵观以上oop和 klass 体系的定义,可以发现,无论是 oop 还是 klass ,基本都被划分为来分别描述 instance 、method 、constantMethod 、methodData 、array 、objArray 、typeArray 、constantPool 、 constantPoolCache 、klass 、compoiledICHolder这几种模型,这几种模型中的每一种都有一个对应的 xxxOopDesc 和对应的 xxxKlass 。通俗而言,这几种模型分别用于描述 Java 类类型和类型指针 、Java   方法类型和方法指针 、常量池类型及指针 、基本数据类型的数组类型及指针 、引用类型的数组类型及指针 、常量池缓存类型及指针、Java类实例对象类型及指针。Hotspot认为使用这几种模型 ,便足以勾画Java程序的全部 :数据、方法 、类型 、数组和实例。

那么oop到底是啥,其存在的意义究竟是什么?其名称已经说得很清楚,就是普通对象指 针。指针指向哪里?指向 klass 类实例。直接这么说可能比较难以理解,举个例子,若 Java 程序中定义了一个类 ClassA ,同时程序中有如下代码 :

Class a = new ClassA ( );

当Hotspot执行到这里时,会先将 ClassA 这个类型加载到 perm 区 ( 也叫方法区 ),然后在 Hotspot 堆中为其实例对象a开辟一块内存空间,存放实例数据。在 JVM加载ClassA到 perm 区时,JVM就会创建一个instanceKlass,instanceKlass中保存了 ClassA 这个 Java 类中所定义的一切信息,包括变量 、方法 、父类 、接 口、构造函数 、属性等,所以 instanceKlass 就是 ClassA这个Java类类型结构的对等体。而 instanceOop  这个“普通对象指针”对象中包含了一个指针,该指针就指向instanceKlass这个实例。在JVM实例化ClassA时,JVM又会在堆中创建一个instanceOop , instanceOop便是 ClassA 对象实例 a 在内存中的对等体,主要存储 ClassA 实例对象的成员变量。 其中,instanceOop 中有一个指针指向 instanceKlass ,通过这个指针,JVM便可以在运行期获取这个类实例对象的类元信息。

oopDesc

既然讲到了oop,就不得不提 JVM中所有oop对象的老祖宗oopDesc类。上述列表里的所有 oopDesc ,诸如 instanceOopDesc 、constantPoolOopDesc 、klassOopDesc 等 ,在 C++的继承体系中,最终全都来自顶级的父类oopDesc ( JDK8中已经没有 oopDesc ,换成了别的名字,但是换汤不换药,内部结构并没有什么太大的变化)。

抛开友元类VMStructs,以及用于内存屏障的_bs , oopDesc类中只剩下了2 个成员变量( 友元类并不算成员变量 ):mark 和 metadata。其中 metadata 是联合结构体,里面包含两个元素 ,分别是 wideKlassOop 与 narrowOop,顾名思义,前者是宽指针,后者是压缩指针。关于宽指针与窄指针这里先简单提一句,主要用于JVM是否对Java class进行压缩,如果使用了压缩技术, 自然可以节省出一定的宝贵内存空间。

oopDesc的这 2 个成员变量的作用很简单,_mark顾名思义,似乎是一种标记,而事实上也的确如此,Java 类在整个生命周期中,会涉及到线程状态 、并发锁 、GC 分代信息等内部标识,这些标识全都打在_mark变量上。而 _metadata顾名思义也很简单,用于标识元数据。每一个 Java 类都会包含一定的变量 、方法 、父类 、所实现的接口等信息,这些均可称为 Java 类的“元数据”,其实可以更加通俗点,所谓的元数据就是在前面反复讲的数据结构。Java类的结构信息在编译期被编译为字节码格式,JVM则在运行期进一步解析字节码格式,从字节码二进制流中还原出一个Java在源码期间所定义的全部数据结构信息,JVM需要将解析出来结果保存到内存中,以便在运行期进行各种操作,例如反射,而_metadata便起到指针的作用,指向 Java 类的数据结构被解析后所保存的内存位置。

仍然以上一节所举的实例化ClassA这个自定义 Java 类的例子进行说明。当JVM完成ClassA类型的实例化之后,会为该 Java 类创建对应的 oop-klass 模型 ,oop 对应的类是 instanceOop ,klass 对应的类是 instanceKlass 。上一节讲过 ,instanceOop 内部会有一个指针指向 instanceKlass ,其实这个指针便是 oopDesc 中所定义的一_metadata。klass 是 Java类型的对等体 ,而 Java 类型 ,便是 Java 编程语言中用于描述客观事物的数据结构,而数据结构包含一个客观事物的全部属性和行为 ,所以叫做 “类元”信息,这便是_metadata的本意。

_metadata的作用可以参考下图所示。

两模型三维度

前文讲过,JVM内部基于oop-klass模型描述一个 Java 类 ,将一个 Java 类一拆为二分别描述,第一个模型是oop,第二个模型是klass。所谓oop,并不是object-oriented programming(面向对象编程),而是ordinary object pointer(普通对象指针),它用来表示对象的实例信息,看起来像个指针,而实际上对象实例数据都藏在指针所指向的内存首地址后面的一片内存区域中。   (理解:oop指向堆中实例对象所在内存区域的首地址)

klass则包含元数据和方法信息,用来描述 Java 类而 klass 则包含元数据和方法信息,用来描述 Java 类或者JVM内部自带的C++类型信息。其实,klass便是前文一直在讲的数据结构,Java 类的继承信息、成员变量 、静态变量 、成员方法 、构造函数等信息都在 klass 中保存 ,JVM据此便可以在运行期反射出Java类的全部结构信息。当然,JVM本身所定义的用于描述Java类的C++类也使用klass去描述,这相当于使用另一种面向对象的机制去描述C++类这种本身便是面向对象的数据。

JVM使用 oop-klass 这种一分为二的模型描述一个 Java 类 ,虽然模型只有两种,但是其实从 3 个不同的维度对一个 Java 类进行了描述。侧重于描述 Java 类的实例数据的第一种模型 oop 主要为 Java 类生成一张 “实例数据视图”,从数据维度描述一个Java类实例对象中各个属性在运行期的值。而第二种模型 klass 则又分别从两个维度去描述一个 Java 类 ,第一个维度是 Java 类的“元信息视图”,另一个维度则是虚函数列表,或者叫作方法分发规则。元信息视图为JVM在运行期呈现Java类的“全息”数据结构信息,这是JVM在运行期得以动态反射出类信息的基础。

下面的图描述了JVM内部对Java类的 “两模型三维度” 的映射。

体系总览

在JVM内部定义了3种结构去描述一种类型 :oop 、klass 和 handle 类。注意,这 3 种数据结构不仅能够描述外在的 Java 类 ,也能够描述 JVM内在的C++类型对象。

前面讲过,klass主要描述 Java 类和 JVM内部C++类型的元信息和虚函数,这些元信息的实际值就保存在oop里面。oop 中保存一个指针指向 klass ,这样在运行期JVM便能够知道每一个实例的数据结构和实际类型。handle是对 oop 的行为的封装,在访问 Java 类时一定是通过 handle 内部指针得到 oop 实例的,再通过 oop 就能拿到 klass ,如此 handle 最终便能操纵 oop 的行为了(注意,如果是调用JVM内部C++类型所对应的oop的函数 ,则不需要通过 handle 来中转,直接通过 oop 拿到指定的 klass便能实现)。klass 不仅包含自己所固有的行为接口,而且也能够操作 Java 类的函数。由于Java 函数在JVM内部都被表示成虚函数,因此handle模型其实就是 Java  类行为的表达。

先上一张图说明这种三角关系。


可以看到,Handle类内部只有一个成员变量一handle,该变量类型是oop*,因此该变量最终指向的就是一个oop的首地址。换言之,只要能够拿到 Handle 对象,便能据此得到其所指向的 oop 对象实例,而通过oop 对象实例又能进一步获取其所关联的 klass 实例,而获取到 klass 对象实例后,便能实现对oop对象方法的调用。因此,虽然从表面上看,handle体系貌似是对 oop 的一种封装 ,但是实际上其醉翁之意在于最终的 klass 体系。

oop一般由对象头、对象专有属性和数据体这 3 部分构成。其一般结构如图所示。

oop体系

所谓oop,就是ordinary object pointer ,也即普通对象指针。但是究竟什么才是普通对象指针呢?要搞清楚何谓 oop ,要问2个问题:

1 ) Hotspot里的 oop 指啥

Hotspot里的oop 其实就是 GC 所托管的指针,每一个 oop 都是一种 xxxOopDesc*类型的指针。所有oopDesc及其子类( 除神奇的 markOopDesc 外 ) 的实例都由 GC 所管理,这才是最最重要的,是 oop 区分 Hotspot 里所使用的其他指针类型的地方。

2)对象指针之前为何要冠以“普通”二字

对象指针从本质上而言就是一个指针,指向xxxOopDesc的指针也是普通得不能再普通的 指针,可是为何在 Hotspot 领域还要加一个“普通”来修饰?要回答这个问题,需要追溯到OOP( 这里的OOP 是指面向对象编程 )的鼻祖SmallTalk 语言。

SmallTalk语言里的对象也由 GC 来管理,但是 SmallTalk 里面的一些简单的值类型对象都会使用所谓的 “直接对象”的机制来实现,例如SmallTalk里面的整数类型。所谓 “直接对象”( immediate object) 就是并不在 GC 堆上分配对象实例,而是直接将实例内容存在对象指针里的对象。这样的指针也叫做 “带标记的指针”(tagged pointer)。

这一点倒是与markOopDesc类型如出一辙,因为 markOopDesc 也是将整数值直接存储在指针里面 ,这个指针实际上并无“指向”内存的功能。

所以在SmallTalk的运行期 ,每当拿到一个对象指针时,都得先校验这个对象指针是一个直接对象还是一个真的指针?如果是真的指针,它就是一个“普通”的对象指针了。这样对象指针就有了“普通”与“不普通”之分。

所以,在Hotspot里面 ,oop 就是指一个真的指针,而 markOop 则是一个看起来像指针但实际上是藏在指针里的对象(数据)。这也正是 markOop 实例不受 GC 托管的原因,因为只要出了函数作用域,指针变量就会直接被从堆枝上释放掉了不需要垃圾回收了。

klass体系

oop的讲述先告一段落 ,再来看看 klass 部分。按照JVM的官方解释,klass主要提供下面2种能力 :

  • ©klass提供一个与 Java 类对等的 C++类型描述。
  • ©klass提供虚拟机内部的函数分发机制 。

其实这种说法与上文所说的2种维度的含义是相同的。klass 分别从类结构和类行为这两方面去描述一个 Java 类 ( 当然也包含JVM内部非开放的C++类)。

与oop相同,在JVM内部也不是klass一个人在战斗,而是一个家族。klass 家族体系如下:

handle体系

前面讲过,handle封装了oop,由于通过oop可以拿到 klass ,而 klass 是对 Java 类数据结构和方法的描述 ,因此 handle 间接封装了 klass。JVM内部使用一个 table 来存储 oop 指针。

如果说oop是对普通对象的直接引用,那么 handle 就是对普通对象的一种间接引用,中间隔了一层。但是JVM内部为何要使用这种间接引用呢?答案是,这完全是为GC考虑。具体表现在2个地方 :

通过handle,能够让 GC 知道其内部代码都有哪些地方持有 GC 所管理的对象的引用,这只需要扫描 handle 所对应的 table ,这样 JVM 便无须关注其内部到底哪些地方持有对普通对象的引用。

在GC过程中如果发生了对象移动(例如从新生代移到了老年代),那么JVM的内部引用无须跟着更改为被移动对象的新地址,JVM 只需要更改 handle table 里对应的指针即可 。

当然实际的handle作为对 Java 类方法的访问的包装,远不止上面所描述的这么简单。这里涉及 Java 类的类继承和接口继承的话题,在 C++领域,类的继承和多态性最终通过vptr(虚函数表)来实现。在klass内部,记录了每一个类的vptr信息,具体而言分为两部分来描述。

1.vtable虚函数表

vtable中存放 Java 类中非静态和非 private 的方法入口,JVM调用 Java 类的方法 (非静态和非 private)时,最终会访问vtable,找到对应的方法入口。

2.itable 接口函数表

itable中存放 Java 类所实现的接口的类方法。同样,JVM调用接口方法时,最终会访问itable,找到对应的接口方法入口。

不过要注意,vtable和itable 里面存放的并不是Java类方法和接口方法的直接入口,而是指向了 Method 对象入口,JVM会通过Method最终拿到真正的 Java 类方法入口,得到方法所对应的字节码/二进制机器码并执行。当然,对于被JIT进行动态编译后的方法,JVM最终拿到的是其对应的被编译后的本地方法的入口。


这里有个问题,前面不是一直在说handle是对 oop 的直接封装和对 klass 的间接封装吗,为什么这里却分别给 oop 和 klass 定义了 2 套不同的 handle 体系呢?这给人的感觉好像是,封 装 oop 的 handle 和封装 klass 的 handle 并不是同一个 handle ,既然不是同一个handle ,那么通 过封装 oop 的handle 还怎么去得到所对应的 klass 信息呢?

其实这正是只怕内部常常容易使人迷惑的地方。在JVM中,使用oop-klass这种一分为二的模型去描述 Java 类以及 只叫内部的特殊类群体,为此JVM内部特定义了各种oop和 klass类型。但是,对于每一个oop,其实都是一个 C++类型,也即 klass;而对于每一个 klass 所对应的 class ,在JVM内部又都会被封装成 oop。只怕在具体描述一个类型时,会使用 oop 去存储这个类型的实例数据,并使用 klass 去存储这个类型的元数据和虚方法表。而当一个类型完成其生命周期后,JVM会触发 GC 去回收,在回收时,既要回收一个类实例所对应的实例数据 oop , 也要回收其所对应的元数据和虚方法表(当然,两者并不是同时回收,一个是堆区的垃圾回收, 一个是永久区的垃圾回收)。为了让 GC 既能回收 oop 也能回收 klass,因此 oop 本身被封装成了 oop ,而 klass 也被封装成 oop。而只叫内部恰好将描述类实例的 oop 全都定义成类名以 oop 结尾的类,并将描述类结构和方法信息的 klass 全都定义成类名以 klass 结尾的类 ,而只怕内部描述类信息的模型恰巧也叫作 oop-klass,与类名存在重合,这就导致了很多人的疑惑,这些疑惑完全是因为叫法上的重合而产生。

因此为了进一步解开疑惑,我们不妨换个叫法,不再将JVM内部描述类信息的模型叫作

oop-klass,而是叫作 data-meta 模型 (瞎取的名字没啥特殊含义)。然后将JVM内部的 oop 体系的类名全都改成以 Data结尾 ,例如,methodData 、instanceData 、constantPoolData 等,同时 将 klass 体系的类名也全都改成以 Meta 结尾,例如methodMeta 、instanceMeta 、constantPoolMeta 等。JVM在进行 GC 时,既要回收 Data 类实例,也要回收 Meta 类实例,为了让 GC 便于回收,因此对于每一个 Data 类和每一个 Meta 类 ,JVM在内部都将其封装成了 oop 模型。对于 Data 类,其内存布局是前面为 oop 对象头 ,后面紧跟实例数据;而对 Meta 类 ,其内存布局是前面为 oop 对象头,后面紧跟实例数据和虚方法表。封装成 oop 之后,再进一步使用 handle 来封装, 于是便有利于 GC 内存回收。

在这种新的模型中,不管是Data类还是 Meta 类,都是一种普通的 C++类型,只不过它们从不同的角度对 Java 类进行了描述。不管是 Data 类还是 Meta 类,当其所在的JVM的内存区域爆满后,都会触 GC,为了方便回收,因此就需要将其封装成 oop。

Java对象模型-oop和klass相关推荐

  1. 区分 JVM 内存结构、 Java 内存模型 以及 Java 对象模型 三个概念

    本文由 简悦 SimpRead 转码, 原文地址 https://www.toutiao.com/i6732361325244056072/ 作者:Hollis 来源:公众号Hollis Java 作 ...

  2. 【转】JVM内存结构 VS Java内存模型 VS Java对象模型

    JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途. 其中有些区域随着虚拟机进程的启动而 ...

  3. java对象模型是什么_蓝石榴_个人博客_JVM内存结构、Java内存模型、Java对象模型...

    JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存 ...

  4. JVM内存结构 VS Java内存模型 VS Java对象模型

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文我们要讨论的JVM内存结构.Java内存模型和Java对象 ...

  5. java对象模型 指令_深入理解多线程(二)—— Java的对象模型

    上一篇文章中简单介绍过synchronized关键字的方式,其中,同步代码块使用monitorenter和monitorexit两个指令实现,同步方法使用ACC_SYNCHRONIZED标记符实现.后 ...

  6. java 堆内存结构_JVM内存结构、Java内存模型和Java对象模型

    JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存 ...

  7. JVM成神之路-Java对象模型

    首先我们要知道: 在jvm的内存结构中,对象保存在堆中,而我们在对对象进行操作时,其实操作的是对象的引用. Java对象包含三个部分 一个Java对象可以分为三部分存储在内存中,分别是:对象头(Hea ...

  8. Java内存结构(JVM)、Java内存模型(JMM)、Java对象模型区别

    目录 一.JAVA内存结构 1.1 JVM启动流程: 1.2 JVM基本结构 1.2.1基本结构图 1.2.2 Java中的内存分配 二.Java内存模型 2.1 主内存和工作内存 2.2 内存间交互 ...

  9. java 类 模型_JVM之Java对象模型

    oop-klass klass 在JVM中,Klass是用来描述一个类的元数据的,里面包括了类的修饰符.类名.父类.类加载器等,简单来说就是描述一个Java类的元数据的数据结构. class Klas ...

最新文章

  1. 如何打开.npz文件
  2. django html5 video,Django Web中的静态文件之HTML5第1篇
  3. DL之NN:利用(本地数据集50000张数据集)调用自定义神经网络network.py实现手写数字图片识别94%准确率
  4. Redhat Linux编译安装LAMP环境
  5. Filter的基本用法一
  6. Reactive(2) 响应式流与制奶厂业务
  7. inchat库下载 python_Linux 环境下安装 Python3 的操作方法
  8. 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (一)
  9. Kaggle比赛(一)Titanic: Machine Learning from Disaster
  10. 去雾综述_图像去雾的算法历史与综述
  11. mysql5.5删除干净_MySql5.5 安装及卸载
  12. sdh管理单元指针_「干货三」SDH技术重点知识分布(附小技巧)
  13. 杭州恒生数米基金网招聘1-3年本科.NET软件工程师
  14. html邮件和英文邮件,英文邮件中Best wishes和Best regards的区别
  15. 三分钟,带你了解PLM
  16. Hybrid Trajectory Planning for Autonomous Driving in On-Road Dynamic Scenarios文章解读
  17. 利用朴素贝叶斯算法解决“公园凉鞋问题”
  18. 【图解CAN总线】-6-classic CAN 2.0总线网络“负载率”计算
  19. Java容器List、Set、Map详解
  20. 2023.1. Stimulsoft 报告和仪表板的新版本:Crack

热门文章

  1. P1242 新汉诺塔
  2. InnoDB还是MyISAM?
  3. CSS实现不固定宽度和高度的自动居中
  4. 避免Eclipse经常出现Out Of Memory
  5. eveningplan
  6. [Leedcode][JAVA][第128题][最长连续序列][Hash]
  7. C++ reverse memcpy
  8. oracle 自动表分析,Oracle自动分析索引,表
  9. 活体检测python_活体检测很复杂?仅使用opencv就能实现!(附源码)!
  10. Asterisk标准通道变量