区分Java内存结构、内存模型和Java对象模型

Java内存结构

       顾名思义,主要是关于Java的运行环境JVM的内存功能划分的概念,一般提到Java内存结构,主要就是说JVM的内存结构。随着JDK版本的迭代更新,JVM内部的结构也在有着不同程度的更新,如JDK7中的Oracle HotSpot 开始移除永久代,JDK8中,永久代被元空间(MetaSpace)所取代等等。所以说Java的内存结构,主要也是说的这方面的概念。它与Java具体的程序运行分析无关,主要是说明了JVM的各个组成部分以及各组成部分的具体作用是什么。

       Java的内存结构可以分为堆内内存和堆外内存。简单来说,堆内内存是Java程序运行时的主要使用空间,它受Java虚拟机的内存管理所控制,对于Java开发人员来说,它是透明的,开发人员无法准确控制。但是可以通过一些手段进行调控,从而使程序性能的性能得以提升。堆外内存是指JVM以外的区域,它不受虚拟机的内存管理所控制,而是有操作系统直接管理。

堆内内存

       这块主要是JVM的内部组成结构,如方法区、堆区、栈区、垃圾回收系统等等。不同的区有不同功能,具体功能这里不再赘述,其中占据最大空间的就是堆区,提到堆区,就不得不提堆区的划分:新生代、老年代(这里将永久代也划分为一种特殊的老年代)。不同的区存储不同“年龄”的对象,JVM的垃圾回收机制,可以根据不同的对象区采用不同的垃圾回收策略。

堆外内存

       JVM的内存管理虽然带来了使用的方便,同时也带来了降低程序性能的问题,这个也是Java一直为大众所诟病的问题。因为各种算法就算再精确,也不可能做到及时有效地回收无用的内存,而且在垃圾回收的时候,甚至会造成其他工作的暂停(Stop the World),这样会严重影响系统的整体性能。另外,对于堆内的内容在flush到远程时,它会先复制到直接内存(即堆外内存),然后再发送,这对性能也有一定的影响。
       Java引入堆外内存,它就解决了上面的两个痛点,首先是垃圾回收问题,堆外内存的垃圾回收不是JVM来完成的,需要开发者自己来管理,具体什么时间进行回收,由开发人员自己决定,这样避免的垃圾回收的时机不准的问题;但是它同样带来了挑战,它对开发人员的水平有一定要求,而且一旦出现问题,一般很难排查;然后是flush到远程时的情况,这里因为它本身使用的就是直接内存,省去了复制的步骤,所以堆外内存的操作一般效率比较高。
       其他内容不再赘述,具体可参考JVM内存结构总结


Java内存模型

概念

       Java的内存模型就是描述Java程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在系统中对变量的存储和取出操作的具体底层细节。它主要针对的是Java在运行期间,数据在内存中的流转过程的具体实现细节。不涉及具体的物理结构,是一种逻辑上的运行规则。

线程工作内存

       而Java的内存结构一般都直接并发相关联。一般Java在工作时,内部线程的工作空间有两部分:线程的本地工作空间(也是线程的数据缓存空间)和公共共享空间(主内存)。线程间的通讯是通过将数据刷到主内存中来完成的。主内存区域是线程间共享的区域,任何线程都可以访问,而本地工作空间是线程私有空间,线程之间不可互相访问。

SR-133内存模型

       SR -133内存模型,这是新的Java内存模型标准(JMM)。它主要的目的就是为了阐述基于内存操作时,进程(线程)之间的可见性原则 。之前老版本的Java内存模型存在很大的争议性,也有很多严重的问题,所以JDK5以后引入了SR-133内存模型。

重排序

       同时提到内存模型就不得不了解重排序概念。它是编译器以及处理器内部的优化过程,也就是说开发人员写出的代码与真正编译后执行的指令顺序可能是不一样的,但是它能保证在单线程下程序的执行结果是不会发生变化的。这些对Java开发人员是透明的,但是如果不了解它,出了问题就很难深究其具体原因。

Volatile和Synchronized

       另外提到并发,就需要了解Java在并发环境下保证执行语句的原子性和可见性方式:volatile(不确保原子性,确保可见性)和synchronized(可以确保原子性和可见性)。

       其它内容就不再赘述,具体可参考:Java内存模型


Java对象模型

概念

       Java的对象模型说的是Java中的对象在内存中的存储结构。它描述的是内存中Java对象的内部组成结构,相比较于前面两个“宏观”概念,它属于“微观”视角。在内存中,一个Java对象包含三部分:对象头、实例数据和对齐填充。 其中对象头是一个很关键的部分,因为对象头中包含锁状态标志、线程持有的锁等标志 。下面基于HotSpot虚拟机来分析Java对象模型。

OOP-Klass Model

       HotSpot是基于C++实现的。这里的OOP指的是普通对象指针(Ordinary Object Pointer)。OOP-Klass结构中包含的内容很多,大都涉及到C++的具体实现。这里只需记住一个结果:在Java程序运行过程中,每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的OOP对象。oop在C++中定义的类型为oopDesc类型,oopDesc是所有OOPS类的共同基类,例如:instanceOopDesc,arrayOopDesc等等,它们的基类都是oopDesc。不同的子类有不同的使用场景。如:instanceOopDesc表示对象实例,即当我们new一个对象的时候,JVM会创建一个instanceOopDesc对象来表示这个Java对象;同理,当我们new一个Java数组的时候,JVM会创建一个arrayOopDesc对象来表示这个数组对象。还有其他各种类型:

//定义了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;

       在HotSpot中,oopDesc、instanceOopDesc和arrayOopDesc分别是定义在不同.hpp文件中的(oopDesc定义在oop.hpp中,instanceOopDesc 定义在instanceOop.hpp中,arrayOopDesc定义在arrayOop.hpp中)。

class oopDesc {
  friend class VMStructs;
  private:
      volatile markOop  _mark;
      union _metadata {
        wideKlassOop    _klass;
        narrowOop       _compressed_klass;
      } _metadata;

  private:
      // field addresses in oop
      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;
}


class instanceOopDesc : public oopDesc {
}

class arrayOopDesc : public oopDesc {
}

       实际上instanceOopDesc里面只是单纯地继承了oopDesc,里面并没有定义其他的数据结构。而从oopDesc源码中可以发现,它里面实际可以分成三块:markOop _mark、共用体 _metadata以及各种field字段。

​ _mark:“标记”的意思,它里面存储了相关对象的锁信息,GC分代信息等。

​ field:都是各种在oop中定义的字段内存地址,都有各自的使用方式,这里不做深入分析。

​ _metadata:它里面有两个内容,_klass(表示普通指针)、_compressed_klass(是压缩类指针)。
这里主要分析klass

Klass

和oopDesc所处的地位一样,Klass类是其他Klass类型的基类。它有两个功能:

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

       其实由此可以推断出,实际上在Java中的类在底层实现上也是一种对象,也就是Klass所对应的实例。例如:instanceKlass类型,它实际上就是在Java中,每加载一个类,都会创建一个instanceKlass对象,所以在底层实现上也可以说类也是一种“对象”,只不过是一种特殊的对象。

       基于Java类也是一种“对象”的观点,自然可以想到:既然也是对象,那么在JVM中,它的存储方式实际上与对象实例的存储方式是相类似的,它也有对应的oop,只不过是klassOop,而且如果是oop结构,那么肯定也会存在相对应的一个klass来描述(klassKlass )。klassKlass也是klass子类的一种。具体结构可以参考下图:
klass链.png

       基于这种设计下,JVM对内存的分配和回收,都可以采用统一的方式来管理。oop-klass-klassKlass关系如图:
oop-klass-klassKlass关系.png

​ 这里有一个很好的例子:

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

       具体存储结构如下图:
对象实例存储结构.jpeg

       具体对象模型分析可以参考博客:深入理解多线程(二)--- Java对象模型深入理解多线程(三)--- Java的对象头

总结:

       在JVM加载类的时候,会创建一个instanceKlass,因为JVM中类加载信息存储在方法区中,所以instanceKlass会在方法区中创建。当我们在Java中new一个对象的时候,无论是普通对象还是数组,都是对象的一种,它会在JVM中创建一个对应的instanceOopDesc对象,来表示Java中的对象,它里面主要结构有三部分:_mark部分(用于存储对象的锁信息,GC分代信息等)、_metadata(共用体,存储一个普通klass指针,指向对象所属类,_compressed_kass,压缩类的指针)。而且在JVM中,实际上类在底层实现上也是一种对象,它也有与Java对象在底层实现上相类似的结构,也会有一个klass来描述所属类型,所以不难想象出,这里的klass实际上就形成了一种链式结构。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339