造火箭 | 全网最细JMM解读

简介: 造火箭 | 全网最细JMM解读

一、引言

在Java的面试中,一般最容易会问的当属JVM的内存模型,而JMM却少有人提起,甚至有些人会认为两者是同一个概念,那么JMM到底指什么呢?

本篇文章将从计算机硬件、操作系统、Java语言层面,一环扣一环地引出Java内存模型存在的意义,让大家对Java内存模型(JMM)有较为深刻的理解。

二、CPU&&内存

众所周知CPU和内存是计算机中两个重要的组成部分,其中CPU负责计算,内存负责存储数据,每次CPU计算前都需要从内存获取数据。我们知道CPU的运行速度远远快于内存的速度,因此会出现CPU等待内存读取数据的情况。

由于两者的速度差距实在太大,为了加快运行速度,于是计算机的设计者在CPU中加了一个CPU高速缓存。这个CPU高速缓存的速度介于CPU与内存之间,每次需要读取数据的时候,先从内存读取到高速缓存中,CPU再从高速缓存中读取。

随着多核技术的出现,CPU的计算能力进一步提高。原本同一时间只能运行一个任务,但现在可以同时运行多个任务。多核CPU的出现,提高了CPU的处理速度,同时也带来了新的问题:缓存一致性。

多核系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。当多个CPU的运算任务都涉及同一块主内存区域时,可能导致各自的缓存数据不一致。如果发生了这种情况,那同步回主内存时以哪个CPU高速缓存的数据为准呢?

首先明确这个是硬件层面的问题,与具体的操作系统和编程语言无关,所以这就需要CPU厂商来做这个事,答案是“缓存一致性协议”。

所谓的缓存一致性协议,指的是在CPU高速缓存与主内存交互的时候,遵守特定的规则,这样就可以避免数据一致性问题了。

在不同的CPU中,会使用不同的缓存一致性协议。例如,MESI协议用于奔腾系列的CPU中,而MOSEI协议则用于AMD系列CPU中,Intel的core i7处理器使用MESIF协议。

至于协议的具体内容,大家可以自行用搜索引擎检索。无外乎是定义了一些内存状态,然后通过消息的方式通知其他CPU高速缓存,从而解决了数据一致性的问题。

三、操作系统

操作系统,它屏蔽了底层硬件的操作细节,将各种硬件资源虚拟化,方便我们进行上层软件的开发,它将硬件进行封装,然后抽象出一些概念,例如CPU时间片、内核态、用户态等。

因此,操作系统抽象出来的CPU与内存也会面临这样的问题——缓存数据一致性。通过把在特定的操作协议下,对特定内存或高速缓存进行读写访问的过程进行抽象,得到的就是操作系统内存模型。无论是MacOS,还是Linux,它们都有着自己特定的内存模型。

Java语言是建立在操作系统上层的高级语言,它只能与操作系统进行交互,而不与硬件进行交互。与操作系统相对于硬件类似,操作系统需要抽象出内存模型,那么Java语言也需要抽象出相对于操作系统的内存模型。

一般来说,编程语言也可以直接复用操作系统层面的内存模型,例如,C++语言就是这么做的。但由于不同操作系统的内存模型不同,有可能导致程序在一套平台上并发完全正常,而在另外一套平台上并发访问却经常出错。因此在某些场景下,就必须针对不同的平台来编写程序。

四、JMM

JMM即Java Memory Model,那么为什么会有它呢?大多数人刚学Java时常会听到的一句英文——“Write once, run anywhere”,简单的说就是为了屏蔽各种硬件或者系统内存的访问差异,实现了Java程序在各种平台都能达到一致的内存访问效果。

完美解决上一小节所说的针对不同的平台来编写程序问题。如果简单的描述JMM定义,那么它是这样的,下图描述了线程、工作内存与主内存的关系,这里明确一点,通常说的JVM内存模型定义的堆、栈、程序计数器等的划分与JMM不是同一层次的内存划分。Java内存模型定义了Java语言如何与内存进行交互,具体地说是Java语言运行时的变量,如何与我们的硬件内存进行交互的。而JVM内存模型,指的是JVM内存是如何划分的。

这里我们也可以立个flag,未来的某一章研究一下JVM内存模型。

JMM的主要目标是定义程序中变量的访问规则,所有的共享变量都存储在主内存中,每个线程都有自己的工作内存,工作内存中保存的是主内存中变量的副本,线程对变量的读写等操作必须在自己的工作内存中进行,而不能直接读写主内存中的变量。

这里我们再简单的看下Java定义内存间的交互操作有哪些?要考,仔细读, JMM总共定义了8种操作,

操作 作用域 用途
lock 主内存 把一个变量标识为一条线程独占的状态
unlock 主内存 把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read 主内存 把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load 工作内存 把read操作从主内存中得到的变量值放入工作内存的变量副本中
use 工作内存 把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
assign 工作内存 把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store 工作内存 把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
write 主内存 把store操作从工作内存中得到的变量的值放入主内存的变量中

不难看出这些操作有些是成对出现的,例如read和load、store和write操作是不允许单独出现的,即不能发生一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况。除了上述的规则外,还有其他的规则如下,

  • 一个线程不能丢弃它的最近的assign操作
  • 没有发生过任何assign操作,数据不能从工作内存同步回主内存中
  • 一个变量实施use、store操作之前,必须先执行过了assign和load操作
  • 同一个时刻只允许一条线程对其进行lock操作,同一线程可重复lock
  • 对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值
  • unlock操作需要在lock之后,一个被其他线程lock的变量只能有其他线程unlock
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)

五、总结

  • 多核CPU和高速缓存在,导致了缓存一致性问题,通过缓存一致性协议解决。
  • 各个操作系统都有各自内存模型,对CPU高速缓存与缓存的读写访问过程进行抽象。
  • Java语言作为运行在操作系统层面的高级语言,为了解决多平台运行的问题,在操作系统基础上进一步抽象,得到了 Java 语言层面上的内存模型。


相关文章
|
1月前
|
存储 安全 Java
揭秘Java序列化神器Serializable:一键解锁对象穿越时空的超能力,你的数据旅行不再受限,震撼登场!
【8月更文挑战第4天】Serializable是Java中的魔术钥匙,开启对象穿越时空的能力。作为序列化的核心,它让复杂对象的复制与传输变得简单。通过实现此接口,对象能被序列化成字节流,实现本地存储或网络传输,再通过反序列化恢复原状。尽管使用方便,但序列化过程耗时且存在安全风险,需谨慎使用。
35 7
|
1月前
|
安全 Java 调度
震撼揭秘!手撕并发编程迷雾,Semaphore与CountDownLatch携手AQS共享模式,让你秒懂并发神器背后的惊天秘密!
【8月更文挑战第4天】在Java并发编程中,AbstractQueuedSynchronizer (AQS) 是核心框架,支持独占锁与共享锁的实现。本文以Semaphore与CountDownLatch为例,深入解析AQS共享模式的工作原理。Semaphore通过AQS管理许可数量,控制资源的并发访问;而CountDownLatch则利用共享计数器实现线程间同步。两者均依赖AQS提供的tryAcquireShared和tryReleaseShared方法进行状态管理和线程调度,展示了AQS的强大功能和灵活性。
32 0
|
3月前
|
安全 算法 C++
C++面试题其三
继续上篇博客的解答,我们将进一步探讨C++中的一些关键概念和常见面试问题。
46 0
|
4月前
|
Java API 开发者
高逼格面试:线程封闭,新名词√
高逼格面试:线程封闭,新名词√
55 0
|
4月前
|
安全 Java 程序员
牛皮了!八年美团大佬耗时3月竟在写《java虚拟机并发编程》
除了咖啡因,我想没有什么能比写出一段执行速度飞快的代码更能令程序员们兴奋了。然而我们如何才能满足这种对计算速度的渴求呢?诚然,摩尔定律可以帮我们解决部分问题,但多核处理器才代表了未来真正的发展方向。
|
11月前
|
Java 程序员
终于不慌内卷了,多亏阿里内部的并发图册+JDK源码速成笔记
并发编程 Java并发在近几年的面试里面可以说是面试热点,每个面试官面试的时候都会跟你扯一下并发,甚至是高并发。面试前你不仅得需要弄清楚的是什么是并发,还得搞清什么是高并发! 在这里很多小白朋友就会很疑惑:我工作又不用,为啥面试总是问?真就内卷卷我呗!(手动狗头)互联网内卷已经是现在的行业趋势,而且是不可逆的,这个大家也知道;但LZ要说的是,虽然简单地增删改查并不需要并发的知识,但是业务稍微复杂一点,你的技术水平稍微提升一点的话你就会知道,并发是我们Java程序员绕不开的一道坎。
43 0
并发程序设计,你真的懂吗?
并发程序设计,你真的懂吗?
89 0
并发程序设计,你真的懂吗?
|
存储 安全 算法
重生之我在人间敲代码_Java并发基础_安全性、活跃性以及性能问题
并发编程中我们需要注意的问题有很多,很庆幸前人已经帮我们总结过了,主要有三个方面,分别是:安全性问题、活跃性问题和性能问题。
|
存储 缓存 Java
反制面试官-14张原理图-再也不怕被问 volatile!
反制面试官-14张原理图-再也不怕被问 volatile!
130 0
反制面试官-14张原理图-再也不怕被问 volatile!
【面试高频题】难度 1.5/5,脑筋急转弯类模拟题
【面试高频题】难度 1.5/5,脑筋急转弯类模拟题