前言
前一阵在看volatile的原理,看到内存屏障和缓存一致性,发现再往底层挖就挖到了硬件和Java内存模型。这一块是自己似懂非懂的知识区,我一般称之为知识混沌区。因此整理这一篇文章。
什么是内存模型(Memory Model)呢?它是系统和程序员之间的规范,它规定了存储器访问的行为,并影响到了性能。并且,Memory Model有多层,处理器规定、编译器规定、高级语。对于高级语言来说,如Java内存模型, 它通常需要支持跨平台,也就是说它会基于各种不同的内存模型,但是又要提供给程序员一个统一的内存模型,可以理解为一个适配器的角色。
这篇文字主要包含了三部分:
-
硬件内存模型
-
Java内存模型和运行时数据区
-
三者的关系
硬件内存模型
下图是2012 Sandy Bridge(一种Intel处理器)核心设计。图中有socket1和socket2,可以理解为了这台电脑中有两个插cpu的槽,每个槽中有一个多核(C1,C2...Cn)的cpu。对于CPU的内存模型可以大致按照如下进行分解:
寄存器 | 编译器会将本地变量和函数参数分配到这些寄存器上 |
内存排序缓冲(Memory Ordering Buffers) | 这些缓冲用于记录等待缓存子系统时正在执行的操作 |
L1 缓存 | 空间最小,访问速度最快 |
L2缓存 | 要作用是作为L1和L3之间的高效内存访问队列。L2缓存同时包含数据和指令 |
L3缓存 | 在同插槽的所有核心都共享L3缓存。 |
主内存 | 在缓存完全没命中的情况下 |
简化之后如下图所示,计算机在高速的 CPU 和相对低速的存储设备(内存,RAM)之间使用高速缓存,作为内存和处理器之间的缓冲。将运算需要使用到的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步回内存之中。
在多处理器的系统中(或者单处理器多核的系统),每个处理器内核都有自己的高速缓存,它们有共享同一主内存(Main Memory---RAM)。
Java内存模型和运行时数据区
说完系统的内存模型,然后开始说一下Java的内存模型(Java Memory Model,简称 JMM)。
Java语言一大特性就是跨平台型。其背后的实现便是在Java 虚拟机规范中定义的Java 内存模型(Java Memory Model,简称 JMM)。
虚拟机通过内存模型,定义了将变量存储到内存和从内存中取出变量的底层细节(字节码指令转换是另一方面),屏蔽掉各种硬件和操作系统的内存访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发程序。
上面提到了两个重要的概念--内存和变量。
先说内存,Java内存模型中的内存分为两类:主内存(Main Memory)和工作内存(Working Memory,又称本地内存)。其详情如下:
主内存可以类比成物理硬件的主内存,但此处仅是虚拟机内存的部分。工作内存可以类比成处理器高速缓存
主内存是所有的线程共享,每个线程都有自己的工作内存,属于线程私有。
一个线程不能访问另一个线程的工作内存,线程之间需要通过主内存来实现线程间的通信;
线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有的操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存的变量;
如果想理解上面提到的变量(Variables)的提取和存储。那就需要继续看一下JVM另一个重要的知识点--运行时数据区。如下图所示,不同jdk版本的运行时数据区划分有所差异。但主要分为了五部分:方法区(8开始使用元数据),堆,栈,程序计数器,本地方法栈。细节就不展开讲。
内存模型中管理的变量(Variables)则主要指是堆中的数据,它包括了实例字段、静态字段和构成数值对象的元素,如数组等。但不包括Java方法中局部变量与方法参数,如果局部变量是一个 reference 类型,它引用的对象在 Java 堆中可被各个线程共享,但是 reference 本身在 Java 栈的局部变量表中。
总结一下:Java 运行时数据区和内存模型是不一样的东西,更确切的说应该是不是同一层次的东西。
-
内存模型:是定义了线程和主内存之间的抽象关系,更多的是定义规则
-
运行时数据区域:是指 JVM 运行时将数据分区域存储,强调对内存空间的划分。
硬件内存模型和Java内存模型关系
Java内存模型中的主内存和工作内存的区别在于线程调度和共享的区别,而不是物理层面的区别。因此硬是将Java内存模型和硬件内存模型进行映射没有意义。如果真的有关系,只能说工作内存更多的是CPU寄存器和缓存。当然,工作内存由于上下文切换等原因,也可能会写回RAM(可以理解为物理内存,严谨来说是物理虚拟内存)。
对于运行时数据区而言,有些是随着虚拟机启动而创建,虚拟机关闭而销毁。还有一部分是随着线程生命周期创建销毁的。
线程间共享的方法区和堆,是说它们会随着虚拟机启动而创建,随着虚拟机退出而销毁。而栈和程序计数器会随着线程开始和结束而创建和销毁。
正如下图所示,方法区和堆在RAM中,也可以在缓存中,这也是JVM创建时进行管理的内存空间。
以堆为例,由于对象实例的创建在JVM中非常频繁,一方面保证并发环境下从堆区中划分内存空间的线程安全,另一方面提升内存分配的吞吐量。对Eden区域继续进行划分,JVM为每个线程分配一个私有缓存区域--本地线程分配缓冲((Thread Local Allocation Buffer,TLAB)。而这个缓冲则缓存中,而不是RAM中
如前所述,Java内存模型和硬件内存架构是不同的。 线程栈和堆的一部分有时可能存在于CPU高速缓存和内部CPU寄存器中。
参考资料:
https://jenkov.com/tutorials/java-concurrency/java-memory-model.html
指令重排序 - 简书
https://www.cnblogs.com/czwbig/p/11127124.html
https://zhuanlan.zhihu.com/p/51613784
Java 运行时数据区和内存模型(JMM)_jmm 和运行时数据区 的关系看-CSDN博客
简单介绍一下什么是“工作内存”和“主内存”(JMM中的概念)_工作内存和主内存-CSDN博客
JVM运行时数据区------堆_数据区和堆-CSDN博客