介绍什么是Java内存模型,和JVM的区别和联系
JMM
JMM与JVM内存结构
- JVM内存结构包括六个部分:堆,方法区(这两者是每个线程私有的),虚拟机栈(Java方法服务),本地方法栈(Native方法服务),程序计数器
- JMM是和多线程相关的一组规范,保证不同JVM运行同一段代码的结果相同,JMM最重要的三点:重排序,原子性,内存可见性
指令重排
JVM和CPU有时为了优化处理速度,会对实际指令执行的顺序进行调整。如下所示:
重排分为三种情况:
- 编译器优化
- CPU重排
- 内存重排
原子性
Java中的原子操作:
- 除了
long
和double
之外的基本类型(int、byte、boolean、short、char、float)的读/写操作,都天然的具备原子性; - 所有引用
reference
的读/写操作; - 加了
volatile
后,所有变量的读/写操作(包含 long 和 double); - 在
JUC原子类
中的一部分方法是具备原子性的,比如 AtomicInteger 的incrementAndGet
方法。 - long和double的读写非原子性操作是因为:它们占用64位的空间,可能被拆分成两个32位进行操作。不过现在主流JVM都会把64位数据的读写作为原子操作。
可见性
图中write()方法是将x赋值为1,read()为读取x,可以看出x初始值为0,对于线程1、2的工作内存而言,都可以从主内存中获取该值。然后线程1执行write()方法将x改成1,这个步骤发生在线程1的工作内存中,所以主内存x依旧为0,因此对于线程2察觉不到x的变化,这就是可见性问题。
主内存与工作内存
共享变量的可见性问题根本上是由CPU与内存之间的多级缓存层引起的。
- 主内存放的是共享变量
- 工作内存放的是共享变量的副本
关系
- 所有变量存储在主内存中,每个线程有独立的工作内存
- 线程不能直接读写主内存中的变量,可以操作自己的工作内存的变量,然后同步到主内存
- 线程间需要通信必须借助主内存中转