当问到 Java 内存模型的时候,一定要注意,Java 内存模型(Java Memory Model,JMM)它和 JVM 内存布局(JVM 运行时数据区域)是不一样的,它们是两个完全不同的概念。

1.为什么要有 Java 内存模型?

Java 内存模型存在的原因在于解决多线程环境下并发执行时的内存可见性和一致性问题。在现代计算机系统中,尤其是多处理器架构下,每个处理器都有自己的高速缓存,而主内存(RAM)是所有处理器共享的数据存储区域。当多个线程同时访问和修改同一块共享数据时,如果没有适当的同步机制,就可能导致以下问题:

  1. 可见性:一个线程对共享变量所做的修改可能不会立即反映到另一个线程的视角中,因为这些修改可能只存在于本地缓存中,并未刷新回主内存。
  2. 有序性:编译器和处理器为了优化性能,可能会对指令进行重排序,这可能导致程序在单线程环境中看似按照源代码顺序执行,但在多线程环境中的实际执行顺序却与预期不同。
  3. 原子性:即使是最简单的读取或赋值操作,在硬件层面也不一定保证是原子性的,即在没有同步的情况下,多线程下可能看到操作只执行了一部分的结果。

Java 内存模型通过定义一套规则来规范并限制编译器、运行时以及处理器对内存访问的重排序行为,确保了多线程间的交互具有明确的语义。它规定了共享变量的访问规则、提供了 happens-before 原则以及 volatile 关键字、synchronized 等工具来实现内存可见性和一致性的保障。这样,程序员在编写并发代码时,可以依据这些规则来确保代码的正确执行,从而避免由于多线程带来的不确定性和错误。

如果没有 Java 内存模型就会出现以下两大问题:

  1. CPU 和 内存一致性问题。
  2. 指令重排序问题。

具体内容如下。

1.1 一致性问题

要讲明白缓存一致性问题,要从计算机的内存结构说起,它的结构是这样的:



所以从上面可以看出计算机的重要组成部分包含以下内容:

  1. CPU
  2. CPU 寄存器:也叫 L1 缓存,一级缓存。
  3. CPU 高速缓存:也叫 L2 缓存,二级缓存。
  4. (主)内存

当然,部分高端机器还有 L3 三级缓存。

由于主内存与 CPU 处理器的运算能力之间有数量级的差距,所以在传统计算机内存架构中会引入高速缓存(L2)来作为主存和处理器之间的缓冲,CPU 将常用的数据放在高速缓存中,运算结束后 CPU 再讲运算结果同步到主内存中,这样就会导致多个线程在进行操作和同步时,导致 CPU 缓存和主内存数据不一致的问题。

1.2 重排序问题

由于有 JIT(Just In Time,即时编译)技术的存在,它可能会对代码进行优化,比如将原本执行顺序为 a -> b -> c 的流程,“优化”成 a -> c -> b 了,但这样优化之后,可能会导致我们的程序在某些场景执行出错,比如单例模式双重效验锁的场景,这就是典型的好心办坏事的事例。

2.定义

Java 内存模型(Java Memory Model,简称 JMM)是一种规范,它定义了 Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,即规范了 Java 虚拟机与计算机内存之间是如何协同工作的。具体来说,它规定了一个线程如何和何时可以看到其他线程修改过的共享变量的值,以及在必须时如何同步地访问共享变量。

3.规范内容

Java 内存模型主要包括以下内容:

  1. 主内存(Main Memory):所有线程共享的内存区域,包含了对象的字段、方法和运行时常量池等数据。
  2. 工作内存(Working Memory):每个线程拥有自己的工作内存,用于存储主内存中的数据的副本,线程只能直接操作工作内存中的数据。
  3. 内存间交互操作:线程通过读取和写入操作与主内存进行交互。读操作将数据从主内存复制到工作内存,写操作将修改后的数据刷新到主内存。
  4. 原子性(Atomicity):JMM 保证基本数据类型(如 int、long)的读写操作具有原子性,即不会被其他线程干扰,保证操作的完整性。
  5. 可见性(Visibility):JMM 确保一个线程对共享变量的修改对其他线程可见。这意味着一个线程在工作内存中修改了数据后,必须将最新的数据刷新到主内存,以便其他线程可以读取到更新后的数据。
  6. 有序性(Ordering):JMM 保证程序的执行顺序按照一定的规则进行,不会出现随机的重排序现象。这包括了编译器重排序、处理器重排序和内存重排序等。

Java 内存模型通过以上规则和语义,提供了一种统一的内存访问方式,使得多线程程序的行为可预测、可理解,并帮助开发者编写正确和高效的多线程代码。开发者可以利用 JMM 提供的同步机制(如关键字 volatile、synchronized、Lock 等)来实现线程之间的同步和通信,以确保线程安全和数据一致性。

内存模型的简单执行示例图如下:

3.1 主内存和工作内存交互规范

为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现(以下内容只需要简单了解即可):

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

PS:工作内存也就是本地内存的意思。

3.2 什么是 happens-before 原则?

happens-before(先行发生)原则是 Java 内存模型中定义的用于保证多线程环境下操作执行顺序和可见性的一种重要手段。

举个例子来说,例如 A happens-before B,也就是 A 线程早于 B 线程执行,那么 A happens-before B 可以保障以下两项内容:

  • 可见性:B 读取到 A 最新修改的值(通过内存屏障)。
  • 顺序性:编译器优化、处理器重排序等因素不会影响先执行 A 再执行 B 的顺序。

课后思考

JMM 和内存屏障有什么关系?happens-before 原则和内存屏障有什么关系?内存屏障的类型又有哪些?

面试官:什么是Java内存模型?的更多相关文章

  1. 求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…

    GitHub 4.1k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.1k Star 的 ...

  2. 一张图,让你和面试官聊一个小时的“Java内存模型”

    如果面试官问你:你了解 Java 内存模型吗? 你就可以使用这张图,按照这张图中的顺序和面试官开聊,正常情况下,聊一个小时是差不多的,这个时候,对你的处境是非常有益的,因为面试官的时间不多了.

  3. 面试官:为什么需要Java内存模型?

    面试官:今天想跟你聊聊Java内存模型,这块你了解过吗? 候选者:嗯,我简单说下我的理解吧.那我就从为什么要有Java内存模型开始讲起吧 面试官:开始你的表演吧. 候选者:那我先说下背景吧 候选者:1 ...

  4. 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)

    并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...

  5. 面试问题:你了解Java内存模型么(Java7、8、9内存模型的区别)

    Java内存模型是每个java程序员必须掌握理解的,这是Java的核心基础,对我们编写代码特别是并发编程时有很大帮助.由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是 ...

  6. 深入理解JVM(6)——Java内存模型和线程

    Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果(“即Ja ...

  7. 【JVM】JVM内存结构 VS Java内存模型 VS Java对象模型

    原文:JVM内存结构 VS Java内存模型 VS Java对象模型 Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清 ...

  8. Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...

  9. Java内存模型相关原则详解

    在<Java内存模型(JMM)详解>一文中我们已经讲到了Java内存模型的基本结构以及相关操作和规则.而Java内存模型又是围绕着在并发过程中如何处理原子性.可见性以及有序性这三个特征来构 ...

  10. 硬件内存模型到 Java 内存模型,这些硬核知识你知多少?

    Java 内存模型跟上一篇 JVM 内存结构很像,我经常会把他们搞混,但其实它们不是一回事,而且相差还很大的,希望你没它们搞混,特别是在面试的时候,搞混了的话就会答非所问,影响你的面试成绩,当然也许你 ...

随机推荐

  1. [转帖]被误解的CPU利用率、超线程、动态调频 —— CPU 性能之迷 Part 1

    https://blog.mygraphql.com/zh/notes/hw/hyper-threading/ 引 性能测试.压力测试.业务系统性能容量评估.这 3 件事,可以认为是大部分程序员/软件 ...

  2. Harbor修改默认网段以及设置开机启动的方法

    Harbor修改默认网段以及设置开机启动的方法 背景 docker 默认的网段是 172.16.xx.xx 网段. harbor进行设置时会自动加1 设置为 172.17.xx.xx 有时候这个网段是 ...

  3. [转帖]THE OSWATCHER ANALYZER USER'S GUIDE

    oswbba THE OSWATCHER ANALYZER USER'S GUIDE Carl DavisMay 7, 2019 To see how to use this tool and it' ...

  4. 【转帖】MySQL索引

    数据表如何用索引快速查找 索引是 排好序的快速查找的数据结构 索引存储在文件系统中 索引的文件存储形式与存储引擎有关 索引数据结构:可以是二叉树.红黑树.Hash表.B-Tree.B+Tree 1.二 ...

  5. [转帖]VMware常用软件ISO下载汇总(2023年6月更新)

    https://www.dinghui.org/vmware-iso-download.html#vcsa-patch 请访问 www.dinghui.org 获得最新版,文章将持续更新.[最后更新时 ...

  6. [转]流程自动化机器人(RPA)概念、原理与实践

    [转]流程自动化机器人(RPA)概念.原理与实践 http://blog.sina.com.cn/s/blog_be0833d00102yho9.html 大多数人每天都会使用到一些机器人流程自动化工 ...

  7. interface{}类型 + fmt.Sprintf() 导致栈逃逸

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 对部分代码进行了栈逃逸检查: go build -gcfl ...

  8. Ubuntu编译Xilinx的u-boot

    博主这里的是Ubuntu20.04LTS+Vivado2017.4+ZedBoard 注意:本文使用的环境变量导入方法是临时的,只要退出当前终端或者使用其他终端就会失效,出现异常问题,请随时expor ...

  9. PLC检测

    填空题1 填空A-E 程序图一 第一训练题的程序,一个是用常开触点,一个是用上升沿指令,建议选上升沿指令编程 程序二 程序图三 程序四 程序五

  10. 手撕Vue-编译模板数据

    经上一篇编译指令数据后,我们已经可以将指令数据编译成具体需要展示的数据了,上一篇只是编译了指令数据,还没有编译模板数据,这一篇我们就来编译模板数据. 也就是 {{}} 这种模板的形式我们该如何编译,其 ...