之前始终把 Java内存模型JVM内存构造 搞混。 其实两者是不同的货色。

Java内存模型(Java Memory Model,JMM) 定义了 java 运行时如何与硬件内存进行交互,比方规定了一个线程如何看到其余内存批改后共享变量的值。一些高级个性也建设在JMM的根底上,比方volatile 关键字。

而 JVM 内存构造, 定义了 JVM 内存是如何划分的。

上面咱们介绍JMM。并从三个方面介绍: 为什么,是什么,解决了什么

JMM

为什么要有Java内存模型

咱们都晓得一台根本的计算机有 CPU 和 内存 两个货色。

CPU负责计算, 内存负责存储运行的程序和数据。

每次CPU计算时它会通过地址总线(Address Bus)向内存发送一个地址信号,指定要读取内存单元的地址。 而后进行期待
之后内存将数据通过数据总线返回给CPU, CPU会将数据加载到寄存器中解决。而后将后果存储回内存。

但随着计算机性能的倒退,CPU的计算速度越来越快。这个期待的工夫(即内存读写的速度)相比计算的工夫变得越来越长,CPU就无奈及时取得须要的数据,导致性能降落。

为了放慢运行的速度,加了一个CPU 高速缓存。每次须要读取数据的时候,先从内存读取到CPU缓存中,CPU再从CPU缓存中读取。因为CPU高速缓存则间接集成在CPU外部,与CPU之间的间隔更近,因而访问速度大大放慢了。

实际上会分为 一级缓存、二级缓存、和三级缓存,并且会有多核CPU。如下图

在多核CPU的状况下,每个CPU都有本人的高速缓存,而它们又共享同一块主内存。 就会产生缓存一致性问题。

比方, 初始化 a = 0;

线程a执行 a = a + 1;

线程b执行 a = a + 1;

多核CPU下可能会产生上面这种状况:

  1. CPU1 执行线程a, 从内存读取 a 为0, 存到高速缓存中。
  2. CPU2 执行线程b, 从内存读取 a 为0, 存到高速缓存中。
  3. CPU1 进行运算,得出后果 1,写回内存 i 的值为 1。
  4. CPU2 进行运算,得出后果 1,写回内存 i 的值为 1。

而在咱们看来,正确后果应该是2.

谬误的起因就是: 两个CPU的高速缓存是互相独立的,无奈感知互相数据变动。

这就造成了缓存一致性问题。

怎么在硬件层面上解决这个问题? 比方 MESI 协定

该协定定义了四种缓存数据状态

  • 批改(Modified):该数据在高速缓存中批改了,还没同步到主内存,数据只存在于本缓存,与主内存不统一
  • 独占(Exclusive): 我刚从内存中读取进去,他人没读过,数据只存在于本缓存中,与主内存统一
  • 共享(Shared): 很多人同时从内存读该数据,数据存在于很多缓存中,与主内存统一
  • 生效(Invalid):有人对该数据操作了,这不是最新的,数据有效

在该协定下,下面执行的运算就变成了这样:

  1. CPU1 执行线程a, 从内存读取 a 为0, 存到高速缓存中。
  2. CPU2 执行线程b, 从内存读取 a 为0, 存到高速缓存中。
  3. CPU1 进行运算,得出后果 1,写回内存 i 的值为 1。通过音讯的形式通知其余持有 i 变量的 CPU 缓存,将这个缓存的状态值为 Invalid。
  4. CPU2 进行运算,从缓存中取值,但该值曾经是Invalid,从新从内存中获取。读取 a为 1, 放入高速缓存
  5. CPU2 进行运算,得出后果 2,写回内存 i 的值为 2。

从下面的例子,咱们能够晓得 MESI 缓存一致性协定,实质上是定义了一些内存状态,而后通过音讯的形式告诉其余 CPU 高速缓存,从而解决了数据一致性的问题。

说了这么多, 到底为什么要有Java 内存模型呢?

起因之一就是下面提到的缓存一致性问题:在不同的 CPU 中,会应用不同的缓存一致性协定。例如 MESI 协定用于奔流系列的 CPU 中,而 MOSEI 协定则用于 AMD 系列 CPU 中,Intel 的 core i7 处理器应用 MESIF 协定。

当然还有其余起因,比方指令重排序导致的可见性问题等

那么怎么对立呢? 要晓得,Java 的最大特点是 Write Once, Run Anywhere

什么是JAVA内存模型

为了解决这个问题,Java封装了一套标准,这套标准就是Java内存模型

Java内存模型 想 屏蔽各种硬件和操作系统的拜访差别,保障了Java程序在各种平台下对内存的拜访都能失去统一成果。目标是解决多线程存在的原子性、可见性(缓存一致性)以及有序性问题。

Java线程之间的通信就是由JMM 管制,从形象的角度来说,JMM定义了线程和主内存之间的形象关系。JMM的形象示意图如图所示

实际上,这个本地内存并不实在存在,只是JMM的抽象概念,它既可能在缓存,也可能在寄存器等。

从图上能够看出,线程A无奈间接拜访线程B的工作内存,线程间通信必须通过主内存。

JMM通过管制主内存与每个线程的本地内存之间的交互,来提供内存可见性保障。

可见性指: 指一个线程批改了共享变量的值后,其余线程是否可能立刻看到这个批改的值。

JAVA内存模型解决了什么

JMM提供的一些高级个性解决了原子性,可见性的问题。

比方当初:

private volatile boolean isRunning = true;    public void run() {        while (isRunning) {            // do some work        }    }    public void stop() {        isRunning = false;    }

isRunning变量被申明为 volatile,这意味着所有的读写操作都是从主存中进行的,而不是从缓存中进行的。

又比方synchronized关键字不仅保障可见性,同时也保障了原子性(互斥性)。在更底层,JMM通过内存屏障来实现内存的可见性以及禁止重排序

比方输出

 private static Object lock = new Object();    static class ThreadA implements Runnable {        @Override        public void run() {            synchronized (lock) {                for (int i = 0; i < 100; i++) {                    System.out.println("Thread A " + i);                }            }        }    }    static class ThreadB implements Runnable {        @Override        public void run() {            synchronized (lock) {                for (int i = 0; i < 100; i++) {                    System.out.println("Thread B " + i);                }            }        }    }    public static void main(String[] args) throws InterruptedException {        new Thread(new ThreadA()).start();        Thread.sleep(10);        new Thread(new ThreadB()).start();    }

这里申明了一个名字为lock的对象锁。咱们在ThreadA和ThreadB内须要同步的代码块里,都是用synchronized关键字加上了同一个对象锁lock

还没写残缺,倡议看https://zhuanlan.zhihu.com/p/29881777

总结

  • 操作系统作为对底层硬件的形象,天然也须要解决 CPU 高速缓存与内存之间的缓存一致性问题。各个操作系统都对 CPU 高速缓存与缓存的读写访问过程进行形象,最终失去的一个货色就是「内存模型」。
  • Java 语言作为运行在操作系统层面的高级语言,为了解决多平台运行的问题,在操作系统根底上进一步形象,失去了 Java 语言层面上的内存模型。
  • JMM是形象的,他是用来形容一组规定,通过这个规定来管制各个变量的拜访形式,围绕原子性、有序性、可见性等开展的。

参考:
http://concurrent.redspider.group/article/02/6.html
https://www.cnblogs.com/chanshuyi/p/deep-insight-of-java-memo...
https://zhuanlan.zhihu.com/p/29881777