内存模型概念:可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象。
JMM(Java内存模型)
为什么需要JMM
在基础知识里提出了线程安全的概念,也解释了为Java出现线程安全问题的原因,那么在计算机层面,为什么会出现线程安全了?
究其原因,其实就是CPU运转太快了,但是绝大多数计算任务并不是只靠CPU就能完成的,处理器至少还要和内存做交互,如读取运算数据、存储运算结果。这个IO操作时很难消除的(无法靠CPU的基础器来完成,毕竟寄存器空间有限)。由于计算器的存储设备与处理器的运算速度有几个数量级的差距,所以现代CPU都会在CPU和内存之间加入高速缓存,如下图,现在计算机都加入了3级缓存,1、2级CPU独有,3级所有CPU共享。
现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓冲:将运算所需要使用的数据复制到缓存中,让运算高速进行,当运算结束后在从缓存同步回内存中,这样处理器就无需等待缓慢的内存读写,释放全部i性能了。
当然高速缓存的加入看似美好,但是也带来了更高的复杂度,因为它引入了一个新问题:`缓存一致性(Cache Coherence)`。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享同一主存(Main Memory),当多个处理器的运算任务都涉及同一区域的时候,将可能导致各自的缓存不一致,比如上篇毕竟发生线程安全问题的图示。
为了解决一致性问题,需要各个处理器访问缓存时遵从一些协议,在读写时根据协议来,这类协议有MSI、MESI等等。
除了增加高速缓存之外,处理器内部为了让运算单元充分利用,处理器还可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但是并不能保证各个语句计算的先后顺序与输入代码中的顺序一致。所以通俗说就是只是保证单线程 执行结果没问题,但是多线程同时设计到多线程通信的情况下就可能有问题了。
与处理器乱序执行优化类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Reorder)优化。
Java内存模型
既然有这么缓存一致性协议,同时各种操作系统和各种硬件存在差异,Java为了让程序在各个平台表现的一样,达成一致的内存访问效果,所以Java就定义了Java内存模型(Java Memory Model,JMM),在此之前,C/C++这些直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上的并发完全正常,而在另一套平台上并发访问经常出错,因为在某些场景就必须针对不同的平台来编写程序、编译程序。总结就是:JMM就是为了java的口号,一次编写,到处运行,且各个平台、硬件运行的结果一样,不会应为平台不同,结果不同。
一般指JMM规范指的是在JDK5,实现了JSR-133发布后的java内存模型,这时候的jmm已经完善和成熟起来。
Java内存模型定义
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
Java内存模型规定所有的变量都储存在主内存(Main Memory)中(此处的主内存和物理硬件的主内存名字一样,两个可以类比,但是此处仅仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(working Memory,可以与前面的高速缓存相对应或类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(取值、赋值等)都必须在工作内存中进行,而不能在操作主内存中的变量。不同的线程之间也无法直接访问对方 的工作内存,线程间变量的传递均需要通过主内存来完成。三个关系如下。
主内存、工作内存和Java内存区域中的堆、栈、方法区等并不是一个层次的划分,两者基本没有关系
如果勉强有关系的话:主内存主要对应的Java堆中的对象实例数据部分,而工作内存则对应虚拟机栈中的部分区域。
更低层次上说:主内存就直接对应物理硬件的内存,而为了获取更好的速度,虚拟级(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要读写的是工作内存。