java对象模型 - CSDN
  • 比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念及...

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


    可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念及其间的区别。甚至我见过有些面试官自己也搞的不是太清楚。不信的话,你去网上搜索Java内存模型,还会有很多文章的内容其实介绍的是JVM内存结构。

    首先,这三个概念是完全不同的三个概念。本文主要对这三个概念加以区分以及简单介绍。其中每一个知识点都可以单独写一篇文章,本文并不会深入介绍,感兴趣的朋友可以加入我的知识星球和球友们共同学习。


    JVM内存结构

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

    其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁。在《Java虚拟机规范(Java SE 8)》中描述了JVM运行时内存区域结构如下:


    各个区域的功能不是本文重点,就不在这里详细介绍了。这里简单提几个需要特别注意的点:

    1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。

    2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是有一定的自由度的。

    3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。

    4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。

    5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。

    6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。

    如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。


    Java内存模型

    Java内存模型看上去和Java内存结构(JVM内存结构)差不多,很多人会误以为两者是一回事儿,这也就导致面试过程中经常答非所为。

    在前面的关于JVM的内存结构的图中,我们可以看到,其中Java堆和方法区的区域是多个线程共享的数据区域。也就是说,多个线程可能可以操作保存在堆或者方法区中的同一个数据。这也就是我们常说的“Java的线程间通过共享内存进行通信”。

    Java内存模型是根据英文Java Memory Model(JMM)翻译过来的。其实JMM并不像JVM内存结构一样是真实存在的。他只是一个抽象的概念。JSR-133: Java Memory Model and Thread Specification 中描述了,JMM是和多线程相关的,他描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。

    那么,简单总结下,Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。

    在JMM中,我们把多个线程间通信的共享内存称之为主内存,而在并发编程中多个线程都维护了一个自己的本地内存(这是个抽象概念),其中保存的数据是主内存中的数据拷贝。而JMM主要是控制本地内存和主内存之间的数据交互的。


    在Java中,JMM是一个非常重要的概念,正是由于有了JMM,Java的并发编程才能避免很多问题。这里就不对Java内存模型做更加详细的介绍了,想了解更多的朋友可以参考《Java并发编程的艺术》。

     Java对象模型



    Java是一种面向对象的语言,而Java对象在JVM中的存储也是有一定的结构的。而这个关于Java对象自身的存储模型称之为Java对象模型。

    HotSpot虚拟机中,设计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。

    每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。

    =

    这就是一个简单的Java对象的OOP-Klass模型,即Java对象模型。

    总结

    我们再来区分下JVM内存结构、 Java内存模型 以及 Java对象模型 三个概念。

    JVM内存结构,和Java虚拟机的运行时区域有关。
    Java内存模型,和Java的并发编程有关。
    Java对象模型,和Java对象在虚拟机中的表现形式有关。

    展开全文
  • Java对象模型-oop和klass

    2019-06-30 10:33:02
    oop-klass模型 Hotspot 虚拟机在内部使用两组类来表示Java对象和类。 oop(ordinary object pointer),用来描述对象实例信息。 klass,用来描述 Java 类,是虚拟机内部Java类型结构的对等体 。 JVM内部定义了...

    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。


     

    展开全文
  • JVM成神之路-Java对象模型

    千次阅读 2018-12-17 14:59:10
    一个Java对象可以分为三部分存储在内存中,分别是:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 对象头(包含锁状态标志,线程持有的锁等标志) 实例数据 对齐填充 oop-klass model(...

    首先我们要知道:

    在jvm的内存结构中,对象保存在堆中,而我们在对对象进行操作时,其实操作的是对象的引用。

    Java对象包含三个部分

    一个Java对象可以分为三部分存储在内存中,分别是:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

    1. 对象头(包含锁状态标志,线程持有的锁等标志)
    2. 实例数据
    3. 对齐填充

    oop-klass model(hotspot jvm中的对象模型)

    Java虚拟机的底层是使用c++实现,而jvm并没有根据一个Java的实例对象去创建对应的c++对象,而是设计了一个oop-klass model ;

    1. OOP(Ordinary Object Pointer):普通对象指针; 表示一个实例信息
    2. Klass:描述对象实例的具体类型, 含了元数据和方法信息
    3. 创建目的:不想让每个对象中都含有一个vtable(虚函数表)

    类就是一类事物的抽象概括。

    OOP体系:

    1. OOPs模块中包含了多个子模块,每个子模块对应一个类型,每一个类型的OOP都代表一个在JVM内部使用的特定对象的类型。
    2. 在Java程序运行过程中,每创建一个新的对象,在JVM内部就会相应的创建一个对应类型的OOP对象。

    代码:

    1. typedef class oopDesc* oop;

    // 定义了oop共同的基类,其他的类型都是它的子类

    1. typedef class instanceOopDesc* instanceOop;

    /* 表示一个Java类型实例,每当我们new一个对象时,

    JVM都会创建一个instanceOopDesc */

    1. typedef class methodOopDesc* methodOop;

    // 表示一个Java方法

    1. typedef class constMethodOopDesc* constMethodOop;

    // 表示一个Java方法中的不变信息

    1. typedef class MethodDataOopDesc* methodDataOop;

    // 记录性能信息的数据结构

    1. typedef class arrayOopDesc* arrayOop;

    /* 定义了数组oops的抽象基类,下面两个类相当于此类的子类,

    new一个数组时会建立此对象*/

    1. typedef class objArrayOopDesc* objArrayOop;

    // 表示持有一个oops数组,对应存储对象的数组

    1. typedef class typeArrayOopDesc* typeArrayOop;

    // 表示容纳基本类型的数组,对应存储基本类型的数组

    1. typedef class constantPoolOopDesc* constantPoolOop;

    // 表示在class文件中描述的常量池

    1. typedef class constantPoolCacheOopDesc* constantPoolCacheOop;

    // 常量池缓存

    1. typedef class klassOopDesc* klassOop;

    // 描述一个与Java类对等的C++类

    1. typedef class markOopDesc* markOop;

    // 表示对象头

    OopDesc结构:

    class oopDesc {

     friend class VMStructs;

            private:

    /*

    * 实际上也是代表了instanceOopDesc、arrayOopDesc和OopDesc

    * 包含了markOop _mark和union_matadata两部分

    */

    volatile markOop _mark; // 保存锁标记、gc分代等信息

    union _metadata { wideKlassOop _klass; // 普通指针,

    // 压缩类指针,和普通指针都指向instanceKlass 对象

    narrowOop _compressed_klass; } _metadata;

    private:

     // 实例数据保存的位置

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

    /* instanceOopDesc和arrayOopDesc都直接继承了oopDesc,

    都没有增加其他的数据结构 */

    class instanceOopDesc : public oopDesc {

    }

    class arrayOopDesc : public oopDesc { }

    1. 职能:表示对象的实例数据,不含任何虚函数
    2. 对象在内存中的基本形式就是oop
    3. 对象所属的类也是一种oop,即klassOop,对应的klass是klassKlass

    Klass体系:

    结构:

    • class Klass;

    // klassOop的一部分,用来描述语言层的类型,其他所有类的父类

    • class instanceKlass;

    // 在虚拟机层面描述一个Java类,每一个已加载的Java类都会创建一个此对象,

    // 在JVM层表示Java类

    • class instanceMirrorKlass;

    // 专有instantKlass,表示java.lang.Class的Klass

    • class instanceRefKlass;

    // 专有instantKlass,表示java.lang.ref.Reference的子类的Klass

    • class methodKlass; // 表示methodOop的Klass
    • class constMethodKlass; // 表示constMethodOop的Klass
    • class methodDataKlass; // 表示methodDataOop的Klass
    • class klassKlass; // 最为klass链的端点,klassKlass的Klass就是它自身
    • class instanceKlassKlass; // 表示instanceKlass的Klass
    • class arrayKlassKlass;

    // 表示arrayKlass的Klass

    • class objArrayKlassKlass; // 表示objArrayKlass的Klass
    • class typeArrayKlassKlass; // 表示typeArrayKlass的Klass
    • class arrayKlass; // 表示array类型的抽象基类
    • class objArrayKlass; // 表示objArrayOop的Klass
    • class typeArrayKlass; // 表示typeArrayOop的Klass
    • class constantPoolKlass; // 表示constantPoolOop的Klass
    • class constantPoolCacheKlass;

    // 表示constantPoolCacheOop的Klass

    功能:

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

    目的:为了实现虚函数多态,提供了虚函数表。

    • instanceKlass的内部结构:
    • objArrayOop _methods;

    // 类拥有的方法

    • typeArrayOop _method_ordering;

    //描述方法顺序

    • objArrayOop _local_interfaces;

    //实现的接口

    • objArrayOop _transitive_interfaces;

    //继承的接口

    • typeArrayOop _fields;

    //域

    • constantPoolOop _constants;

    //常量

    • oop _class_loader;

    //类加载器

    • oop _protection_domain;

    //protected域

    ....

    HotSpotJVM的设计这把对象一拆为二,分为Klass和oop,其中oop的只能主要在于表示对象的实例数据,所以其中不含有任何虚函数,而klass为了实现虚函数多态,所以提供了虚函数表。所以,关于java的多态,其实也有虚函数的影子在。

    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。那么,对象所属的类,在JVM中也是一种对象,因此它们实际上也会被组织成一种oop,即klassOop。同样的,对于klassOop,也有对应的一个klass来描述,它就是klassKlass,也是klass的一个子类。

    klassKlass作为oop的klass链的端点。关于对象和数组的klass链大致如下图:

    oop-klass-klassKlass关系图:

    符号引用

    符号引用就是用一组符号来描述所引用的目标,我们都知道在Java中,通常情况下我们写的一个Java类被编译以后都是一个class文件,在编译的时候,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。

    一般一个对象的创建就是从new开始的,而操作这些创建指令的就是jvm了,首先当你开始new的时候,jvm会先去查找一个符号引用,如果找不到这个符号引用就说明这个类还没有被加载,因此jvm就会进行类加载,然后符号引用被解析完成,紧接着jvm会为对象在堆内存中分配内存,也就是说我们这个user对象就在堆内存中有一块内存空间了。

    HotSpot虚拟机实现的Java对象包括三个部分:对象头,实例字段和对齐填充

    为对象分配完堆内存之后,jvm会将该内存进行零值初始化。

    内存存储:

    关于一个Java对象,他的存储是怎样的,一般很多人会回答:对象存储在堆上。稍微好一点的人会回答:对象存储在堆上,对象的引用存储在栈上。今天,再给你一个更加显得牛逼的回答:

    对象的实例(instantOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。

    其实如果细追究的话,上面这句话有点故意卖弄的意思。因为我们都知道。方法区用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 所谓加载的类信息,其实不就是给每一个被加载的类都创建了一个 instantKlass对象么。

    示例

    class Model{
    public static int a = 1;
    private final int NUMBER = 2;
    public int b;
    public int c = 3;
    
    public Model(int b){
    this.b = b;
    }
    
    public static void main(String[] args){
    int d = 10;
    Model modelA = new Model(2);
    Model modelB = new Model(3);
    }
    }
    

    存储结构:

    总结:

    在Java中,JVM中的对象模型包含两部分:Oop和Klass,在类被加载的时候,JVM会给类创建一个instanceKlass,其中包含了类信息、常量、静态变量、即时编译器编译后的代码等,存储在方法区,用来在JVM层表示该Java类。而使用new一个对象后,JVM就会创建一个instanceOopDesc对象,该对象包含对象头和实例数据,对象头中保存的是锁的状态标志等信息,元数据则实际上是一个指针,指向instanceKlass

    Java对象模型---对象头(Mark Word)

    对象自身的运行时数据

    这部分存储包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据被官方称为Mark Word,在32位和64位的虚拟机中的大小分别为32bit和64bit。

    由于对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以提高存储空间的利用率。即这部分数据会根据对象的状态来分配存储空间。

    对象的类型指针

    即指向对象的类元数据的指针。虚拟机可以通过该指针判定对象实例属于哪个类。

    在Java对象中比较特殊的是Java数组,一个数组实例的对象头中必须记录数组的长度。JVM可以通过对象头中的数组长度数据来判定数组的大小,这是访问数组类型的元数据无法得到的。

    对象的实例数据

    前面提到对象头是对象的额外开销,只有实例数据才是一个对象实例存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分内容同时记录了子类从父类继承所得的各类型数据。

    填充

    对齐填充在对象数据中并不是必然的,只是起着占位符的作用,没有特别含义。HotSpot要求对象起始地址必须是8字节的整数倍。对象头的大小刚好符合要求,因此当实例数据没有对齐时,就需要通过填充来对齐数据。

    获取类的元数据

    虚拟机在加载类的时候会将类的信息、常量、静态变量和即时编译器编译后的代码等数据存储在方法区(Method Area)。类的元数据,即类的数据描述,也被存在方法区。我们知道对象头中会存有对象的类型指针,通过类型指针可以获取类的元数据。因此,对象的类型指针其实指向的是方法区的某个存有类信息的地址。

    但是,并不是每个对象实例都存有对象的类型指针。根据对象访问定位方法的不同,对象的类型指针被存放在不同的区域。

    • 通过句柄访问对象
      • 对象的类型指针被存放在句柄池中;
    • 通过Reference指针直接访问对象
      • 对象的类型指针被存放在对象本身的数据中。

    比较来说:

    • 使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,当对象被移动(垃圾收集时会经常移动对象)时智慧改变句柄中实例数据执政,而reference本身不需要修改。
    • 使用直接指针访问方式的最大好处就是速度快,节省了一次指针定位的时间开销(对象的访问在java中也非常频繁)

    因此,Java的对象数据存储可以理解为:

    • 引用类型(指向对象的Reference)
      • 存储在栈中
    • 对象的类的元数据 (Class MetaData)
      • 存储在方法区中
    • 对象的实例数据
      • 存储在堆中

    对象内存布局

    • 存储的是与对象本身定义的数据无关的额外存储成本,其数据结构不固定。
    • 32位JVM中,对象不同装填的mark word各个比特位区间图示如下:

    对象五种状态:无锁态、轻量级锁、重量级锁、GC标记和偏向锁。

    HotSpot中对象头主要包含两部分

    第一:

    用于存储对象自身的运行时数据,如上表中的对象哈希码,对象分代年龄,偏向线程id,偏向时间戳等。

    第二:

    类型指针了,我们看表中也有指针字样,那么这部分主要就是杜希昂指向它的类元数据的指针了,虚拟机就是通过这个指针来确定这个对象时那个实例。

    偏向锁和重量级锁

    • 偏向锁:主要解决无竞争下的锁性能问题
      • 按照HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作,CAS操作会延迟本地调用
      • 偏向锁会偏向第一个访问锁的程序,如果接下来的运行过程中,该锁没有被其他线程访问,则持有偏向锁的线程将永远不需要触发同步。
      • 但是如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁
      • 只能在单线程中起作用
    • 轻量级锁:为了在无多线程竞争的环境中使用CAS来替代synchronized。减少传统的重量级锁使用操作系统互斥量产生的性能消耗,是为了减少多线程进入互斥的几率。并非替代互斥。

    参考资料:

    《深入理解Java虚拟机》

    http://www.cnblogs.com/chenyangyao/p/5245669.html

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

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

     

    展开全文
  • Java对象模型

    2018-07-27 14:03:47
    1.【理解HotSpot虚拟机】对象在jvm中的...HotSpot是基于c++实现,而c++是一门面向对象的语言,本身具备面向对象基本特征,所以Java中的对象表示,最简单的做法是为每个Java类生成一个c++类与之对应。 但HotSpot ...

    转载:http://blog.csdn.net/linxdcn/article/details/72850375

    1.【理解HotSpot虚拟机】对象在jvm中的表示:OOP-Klass模型

    1 概述

    HotSpot是基于c++实现,而c++是一门面向对象的语言,本身具备面向对象基本特征,所以Java中的对象表示,最简单的做法是为每个Java类生成一个c++类与之对应。

    但HotSpot JVM并没有这么做,而是设计了一个OOP-Klass Model。这里的 OOP 指的是 Ordinary Object Pointer (普通对象指针),它用来表示对象的实例信息,看起来像个指针实际上是藏在指针里的对象。而 Klass 则包含元数据和方法信息,用来描述Java类。

    之所以采用这个模型是因为HotSopt JVM的设计者不想让每个对象中都含有一个vtable(虚函数表),所以就把对象模型拆成klass和oop,其中oop中不含有任何虚函数,而Klass就含有虚函数表,可以进行method dispatch。

    2 OOP-Klass模型

    2.1 Klass

    Klass简单的说是Java类在HotSpot中的c++对等体,用来描述Java类。

    Klass主要有两个功能:

    • 实现语言层面的Java类
    • 实现Java对象的分发功能

    那Klass是什么时候创建的呢?一般jvm在加载class文件时,会在方法区创建instanceKlass,表示其元数据,包括常量池、字段、方法等。

    2.2 OOP

    Klass是在class文件在加载过程中创建的,OOP则是在Java程序运行过程中new对象时创建的。

    一个OOP对象包含以下几个部分:

    • instanceOopDesc,也叫对象头 
    • Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等
    • 元数据指针,即指向方法区的instanceKlass实例
    • 实例数据

    3 实例说明

    假如我们有如下代码:

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

     

    上述代码得OOP-Klass模型入下所示

     

     

    展开全文
  • JAVA对象模型

    千次阅读 2018-06-09 22:16:30
    现在我们来好好的看看Java对象模型: 几乎所有的Java对象保存在堆内存中(有例外,自行了解),在内存中Java对象包含三部分:对象头、实例数据和对齐填充。其中对象头是一个很关键的部分,因为对象头中包含锁状态...
  • 深入理解多线程(二)— Java对象模型 深入理解多线程(一)—Synchronized的实现原理文章中我们简单介绍过synchronized关键字,通过介绍我们了解到同步代码块使用monitorenter和monitorexit两个指令实现,同步...
  • JVM这块知识绝对是学习java过程中的重点和难点,我习惯把这块的知识叫做javaSE高级基础,在学习jvm这块的知识,你一定会遇到几个概念,那就是java内存结构,java内存模型,java对象模型和jvm内存结构!而这几个概念...
  • 比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念及...
  • 现如今你是否有这样的感觉,无论生活还是学习,节奏都是非常的快,每天面对海量的知识信息,自己感觉都要hold不住了,每天打开微信公众号,是不是发现有几十条未读,无论是技术文章还是其他类型的文章,我们大多失去...
  • 比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念...
  • 前面几篇文章中, 系统的学习了下JVM内存结构、Java内存模型、Java对象模型, 但是发现自己还是对这三者的概念和区别比较模糊, 傻傻分不清楚。所以就有了这篇文章, 本文主要是对这三个技术点再做一个总结和区分, 加深...
  • JVM内存结构 VS Java内存模型 VS Java对象模型
  • Java内存模型详解

    千次阅读 多人点赞 2020-02-16 09:02:08
    前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的一个,而且涉及到很多...
  • 全面理解Java内存模型

    万次阅读 多人点赞 2020-04-27 22:37:02
    Java内存模型Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果我们要想深入了解Java并发编程,就要先理解好Java内存...
  • 前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的一个,而且涉及到很多...
  • JVM基础知识(三)Java对象模型

    千次阅读 2019-03-01 10:28:13
    java对象 在内存中,一个Java对象包含三部分:对象头、实例数据和对齐填充。而对象头中又包含锁状态标志、线程持有的锁等标志。 oop-klass model OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来...
  • Java对象模型回顾与勘误 在上一篇文章中,关于对象头的部分描述有误,我已经在我博客的文章中就行修正 。这里再重新表述一下。 每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlas...
  • Java工程师成神之路(2018修订版)

    万次阅读 2018-03-19 18:33:22
    主要版本更新时间备注v1.02015-08-01首次发布v1.12018-03-18增加新技术知识、完善知识体系一、基础篇JVMJVM内存结构堆、栈、方法区、直接内存、堆和栈区别Java内存...JVM参数及调优Java对象模型oop-klass、对象头Hot...
  • JAVA对象所占字节大小计算方法

    千次阅读 2010-09-16 22:09:00
    该类为cache4j缓存框架中的工具类方法,该方法实现了两个接口 接口1:计算对象在内存中所占字节数 接口2:复制对象,实现深度克隆效果,实现原理为先序列化对象,然后在反序列化对象;返回一个新的...
  • java内存模型概述

    千次阅读 2018-08-25 16:52:11
    java内存模型  为了控制线程之间的通信,(完成底层封装)  用来屏蔽掉各种硬件和操作系统之间的内存访问差异,以实现让Java程序在各平台下都能达到一致的内存访问效果。 JMM目标:定义程序中各个变量的访问...
1 2 3 4 5 ... 20
收藏数 424,565
精华内容 169,826
热门标签
关键字:

java对象模型