本文的目的,搞懂三个问题:

  1. JMM和硬件的关系?
  2. JMM如何保证并发编程三个重要特征?
  3. JVM内存区域和JMM的关系?

1.硬件架构

假如是要设计Java虚拟机,那么最先要分析的一定是计算机硬件情况。

【JVM】第十一篇:JMM与JVM关系(附整体架构图)

由于CPU速度快,所以在内存之前会有一个CPU缓存(一般分为L1,L2,L3),所以这时就会存在内存与CPU缓存一致性的问题。
【JVM】第十一篇:JMM与JVM关系(附整体架构图)

  • 解决方案一:总线加锁(BUS),但是会降低CPU吞吐量
  • 解决方案二:MESI协议。比如,当CPU在CACHE中操作数据时,如果该数据是共享变量,数据在CACHE读到寄存器中,进行新修改,并更新内存数据,CACHE LINE置为无效,其他的CPU在内存中读取数据。

2.JMM

JMM只是一个模型,并不实际存在,可以看做是对硬件架构的抽象:

【JVM】第十一篇:JMM与JVM关系(附整体架构图)

  • 主内存:共享的信息
  • 工作空间:私有信息,存放的数据分为两类
    • 基本数据类型:直接分配内存到工作内存
    • 引用类型:引用的地址存放在工作内存,引用的对象放在堆中
  • 工作方式:
    • 线程修改私有数据:直接在工作空间修改
    • 线程修改共享数据:把数据复制到工作空间中,在工作空间中修改,修改完成以后刷新内存中的数据

那硬件架构都有MESI协议什么的保证数据一致,JMM如何保证呢?==> Java内存模型的必要性:规范内存数据与工作空间的数据交互!!

单线程就没什么说的了,JMM如何保证并发特征的?

  • 原子性
    • 不可分割(x=10是原子性;y=x没有原子性;i++没有原子性)
    • 保证方案:Synchronized + JUC#Lock
  • 可见性
    • 线程只能操作自己工作空间中的数据
    • 保证方案:volatile + synchronized
  • 有序性:
    • 为了提高执行效率,会有编译重排序和指令重排序,所以程序中的顺序不一定就是执行的顺序
    • 保证方案:
      • 线程内:as-if-seria,单线程中重拍后不影响执行结果
      • 多线程:happens-before规则

PS:happens-before 规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程任意后续操作
  • start()规则:如果线程A执行操作threadB.start(),那么A线程中threadB.start()happens-beforeB的任意操作
  • join()规则:如果线程A执行操作thread.join(),那么线程B的任意操作happens-before于A从threadB.join()返回
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
  • 监视器锁规则:对一个锁(synchronized)的解锁,happens-before于随后对这个锁的解锁
  • 传递性:如果A happens-before B,B happens-before C ,那么 A happens-before C

3.JVM

JMM和JVM什么关系呢?JMM只是一个抽象模型,而JVM是一个具体的实现,跟硬件架构一样。可以理解成JMM是mvc,JVM是SSM。

【JVM】第十一篇:JMM与JVM关系(附整体架构图)

  • 方法区(线程共享):类信息、常量、JIT编译后的数据
  • Java堆区(线程共享):实例对象 GC (OOM)
  • VM stack(线程私有):Java方法在运行的内存模型 (OOM)
  • PC(线程私有):Java线程的私有数据,这个数据就是执行下一条指令的地址
  • Native method stack(线程私有):与JVM的native

==>JMM、JVM结构总图

【JVM】第十一篇:JMM与JVM关系(附整体架构图)

  • JMM:粉框内部对应JMM的三部分
  • JVM:类加载器 + 运行时数据区 + 执行引擎

图中需要注意的几点:

  • 执行引擎:可以理解成CPU

    • JIT(编译执行)+ 解释执行
    • 1比1线程模型,一个线程对应一个OS线程
    • 由于映射到本地线程,所以java创建的线程受CPU调度,即抢占式基于优先级队列的调度策略
    • 线程有两个态:用户态(低权限,只能控制自己),内核态(高权限,如线程创建,阻塞)
  • Class:Klass的基本镜像,将类加载进来时创建

    • Class对象是方法区Klass对外的窗口,堆对象的创建以及反射机制获取类信息都是通过Class对象完成
    • static类变量在Class中
    • 双向:Klass对象与Class对象都相互持有各自的引用

图中虚线是几个操作的流程:

  • 创建对象的流程:

    1. 检验是否存在该对象的Class对象,若不存在则通过类加载器将相应class文件加载进来,在方法区生成Klass对象

    2. 在堆上生成Class对象

    3. 根据Class对象创建Object实例 —> DCL单例问题

      1. 创建对象
      2. 进行初始化
      3. 分配内存并放入:栈空间,线程本地堆内存,堆内存
    4. 返回Object的指针OOP到栈空间对应线程

  • 访问一个对象的流程

    1. 获取程序计数器中的指令,执行一条+1

    2. 线程根据引用获取到对象

    3. 通过对象头的Klass Pointer指针获取到成员变量等信息

      注:还有一种句柄式,即线程直接持有KlassPointer

  • 什么时候发生并发:

    • 同时操作同一对象的同一成员变量数据
    • 一般情况是运行的同一方法时

相关文章: