本文的目的,搞懂三个问题:
- JMM和硬件的关系?
- JMM如何保证并发编程三个重要特征?
- JVM内存区域和JMM的关系?
1.硬件架构
假如是要设计Java虚拟机,那么最先要分析的一定是计算机硬件情况。
由于CPU速度快,所以在内存之前会有一个CPU缓存(一般分为L1,L2,L3),所以这时就会存在内存与CPU缓存一致性的问题。
- 解决方案一:总线加锁(BUS),但是会降低CPU吞吐量
- 解决方案二:MESI协议。比如,当CPU在CACHE中操作数据时,如果该数据是共享变量,数据在CACHE读到寄存器中,进行新修改,并更新内存数据,CACHE LINE置为无效,其他的CPU在内存中读取数据。
2.JMM
JMM只是一个模型,并不实际存在,可以看做是对硬件架构的抽象:
- 主内存:共享的信息
- 工作空间:私有信息,存放的数据分为两类
- 基本数据类型:直接分配内存到工作内存
- 引用类型:引用的地址存放在工作内存,引用的对象放在堆中
- 工作方式:
- 线程修改私有数据:直接在工作空间修改
- 线程修改共享数据:把数据复制到工作空间中,在工作空间中修改,修改完成以后刷新内存中的数据
那硬件架构都有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。
- 方法区(线程共享):类信息、常量、JIT编译后的数据
- Java堆区(线程共享):实例对象 GC (OOM)
- VM stack(线程私有):Java方法在运行的内存模型 (OOM)
- PC(线程私有):Java线程的私有数据,这个数据就是执行下一条指令的地址
- Native method stack(线程私有):与JVM的native
==>JMM、JVM结构总图
- JMM:粉框内部对应JMM的三部分
- JVM:类加载器 + 运行时数据区 + 执行引擎
图中需要注意的几点:
-
执行引擎:可以理解成CPU
- JIT(编译执行)+ 解释执行
- 1比1线程模型,一个线程对应一个OS线程
- 由于映射到本地线程,所以java创建的线程受CPU调度,即抢占式基于优先级队列的调度策略
- 线程有两个态:用户态(低权限,只能控制自己),内核态(高权限,如线程创建,阻塞)
-
Class:Klass的基本镜像,将类加载进来时创建
- Class对象是方法区Klass对外的窗口,堆对象的创建以及反射机制获取类信息都是通过Class对象完成
- static类变量在Class中
- 双向:Klass对象与Class对象都相互持有各自的引用
图中虚线是几个操作的流程:
-
创建对象的流程:
-
检验是否存在该对象的Class对象,若不存在则通过类加载器将相应class文件加载进来,在方法区生成Klass对象
-
在堆上生成Class对象
-
根据Class对象创建Object实例 —> DCL单例问题
- 创建对象
- 进行初始化
- 分配内存并放入:栈空间,线程本地堆内存,堆内存
-
返回Object的指针OOP到栈空间对应线程
-
-
访问一个对象的流程
-
获取程序计数器中的指令,执行一条+1
-
线程根据引用获取到对象
-
通过对象头的Klass Pointer指针获取到成员变量等信息
注:还有一种句柄式,即线程直接持有KlassPointer
-
-
什么时候发生并发:
- 同时操作同一对象的同一成员变量数据
- 一般情况是运行的同一方法时