思考:Java内存模型和硬件内存模型

前言

前一阵在看volatile的原理,看到内存屏障和缓存一致性,发现再往底层挖就挖到了硬件和Java内存模型。这一块是自己似懂非懂的知识区,我一般称之为知识混沌区。因此整理这一篇文章。

什么是内存模型(Memory Model)呢?它是系统和程序员之间的规范,它规定了存储器访问的行为,并影响到了性能。并且,Memory Model有多层,处理器规定、编译器规定、高级语。对于高级语言来说,如Java内存模型, 它通常需要支持跨平台,也就是说它会基于各种不同的内存模型,但是又要提供给程序员一个统一的内存模型,可以理解为一个适配器的角色。

这篇文字主要包含了三部分:

  1. 硬件内存模型

  2. Java内存模型和运行时数据区

  3. 三者的关系

硬件内存模型

下图是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 运行时数据区和内存模型是不一样的东西,更确切的说应该是不是同一层次的东西。

  1. 内存模型:是定义了线程和主内存之间的抽象关系,更多的是定义规则

  2. 运行时数据区域:是指 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博客

相关推荐
Swift社区3 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht3 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht3 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20243 小时前
Swift 数组
开发语言
吾日三省吾码4 小时前
JVM 性能调优
java
stm 学习ing4 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc5 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi776 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器