Java内存模型是什么

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 本文介绍了Java并发编程中重要的Java内存模型(JMM),该模型基于硬件内存模型,旨在解决CPU缓存一致性与处理器重排序问题,确保多线程环境下的原子性、可见性和有序性。文章首先讲解了CPU执行过程中的高速缓存和由此引发的缓存一致性问题,以及处理器的重排序现象。接着,引入了计算机内存模型,它是处理这些问题的操作规范。随后,阐述了Java内存模型,其规定了变量存储在主存,线程有自己的工作区,通过主存实现线程间通信,从而在Java层面保证内存一致性。最后,对比了JMM和计算机内存模型的异同,强调两者作用于不同层次的内存一致性保障。

前言

要想深入了解Java并发编程,就要先理解好Java内存模型,而要理解Java内存模型又不得不从硬件、计算机内存模型说起,本文从计算机内存模型产生的原因、解决的问题谈起,然后再对Java模型进行介绍,最后对计算机内存模型和Java内存模型进行总结,希望大家看完本文之后有所收获!

CPU工作过程及出现的问题

CPU执行过程

大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道,而计算机上面的临时数据,是储存在主存中的。

计算机内存包括高速缓存主存

我们知道CPU执行指令的速度比从主存读取数据和向主存写入数据快很多,所以为了高效利用CPU,CPU增加了**高速缓存(cache)**来匹配CPU的执行速度,最终程序的执行过程如下

  1. 首先会将数据从主存中复制一份到CPU的高速缓存中
  2. 当CPU执行计算的时候就可以直接从高速缓存中读取数据和写入数据
  3. 当运算结束后,再将高速缓存的数据更新到主存中

缓存一致性问题

上面的执行过程在单线程情况下并没有问题,但是在多线程情况下就会出现问题,因为CPU如果含有多个核心,则每个核心都有自己独占高速缓存,如果出现多个线程同时执行同一个操作,那么结果是无法预知。例如2个线程同时执行i++,假设i的初始值是0,那么我们希望2个线程执行完成之后i的值变为2,但是事实会是这样吗?

可能出现的情况有:

  1. 线程1先将i=0从主存中读取到线程1的高速缓存中,然后CPU完成运算,再将i=1写入到主存中,然后线程2开始从主存中读取i=1到线程2的高速缓存中,然后CPU完成运算,再将i=2写入到主存中,那么i=2即为我们想要的结果。
  2. 线程1将i=0从主存中读取到线程1的高速缓存中的同时线程2也从主存中读取i=0到线程2的高速缓存中,然后线程1和线程2完成运算后,也都将i=1写入到主存中,那么结果i=1,结果就不是我们想要的了。出现这个情况,我们称为缓存不一致问题

那么如何解决CPU出现的缓存不一致问题呢?通常使用的解决方法有2种:

  1. 通过给总线加锁
  2. 使用缓存一致性协议

1种方法虽然也达到了目的,但是在总线被锁住的期间,其他的CPU也无法访问主存,效率很低,所以就出现了缓存一致性协议即第2种方法,其中最出名的就是Intel的MESI协议,MESI协议保证每个CPU高速缓存中的变量都是一致的。它的核心思想是,当CPU写数据时候,如果发现操作的变量是共享变量(即其他CPU上也存在该变量),就会发出信号通知其他CPU将它高速缓存中缓存这个变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己高速缓存中缓存该变量的缓存行为无效状态,那么它就会从主存中重新读取

处理器重排序问题

在多线程场景下,CPU除了会出现缓存一致性问题,还会出现因为处理器重排序处理器(CPU)为了提高效率可能会对输入的代码进行乱序执行,而造成多线程的情况下出现问题。 例如:

复制代码

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

线程1由于处理器重排序,先执行性了语句2,那么此时线程2会认为context已经初始化完成,那么跳出循环,去执行doSomethingwithconfig(context)方法,实际上此时context并未初始化(即线程1的语句1还未执行),而导致程序出错。

什么是计算机内存模型

上面提到的缓存一致性问题处理器重排序问题都是在多线程情况下CPU可能出现的问题,那我们应该怎么处理这些问题?实际上这些问题并不需要我们考虑,这些问题CPU都会处理好,而CPU处理这些问题的时候是按照特定的操作规范,对特定的主存进行访问或告诉CPU高速缓存怎么访问主存,保证了多线程场景下的原子性、可见性、有序性,这个操作规范就称为计算机内存模型

可见性即当一个变量修改后,这个变量会马上更新到主存中,其他线程会收到通知这个变量修改过了,使用这个变量的时候重新去主存获取

什么是Java内存模型

从前面的介绍了解到计算机内存模型是一种解决多线程场景下的一个主存操作规范,既然是规范,那么不同的编程语言都可以遵循这种操作规范,在多线程场景下访问主存保证原子性、可见性、有序性。

Java内存模型(Java Memory Model,JMM)即是Java语言对这个操作规范的遵循,JMM规定了所有的变量都存储在主存中,每个线程都有自己的工作区,线程将使用到的变量从主存中复制一份到自己的工作区,线程对变量的所有操作(读取、赋值等)都必须在工作区,不同的线程也无法直接访问对方工作区,线程之间的消息传递都需要通过主存来完成。可以把这里主存类比成计算机内存模型中的主存,工作区类比成计算机内存模型中的高速缓存

而我们知道JMM其实是工作主存中的,Java内存模型中的工作区也是主存中的一部分,所以可以这样说Java内存模型解决的是内存一致性问题(主存和主存)而计算机内存模型解决的是缓存一致性问题(CPU高速缓存和主存),这两个模型类似,但是作用域不一样,Java内存模型保证的是主存和主存之间的原子性、可见性、有序性,而计算机内存模型保证的是CPU高速缓存和主存之间的原子性、可见性、有序性。

转载来源:https://juejin.cn/post/6844903794644353038

相关文章
|
3天前
|
存储 缓存 Java
Java中的缓冲流提升I/O性能,通过内存缓冲区减少对硬件访问
【6月更文挑战第22天】Java中的缓冲流提升I/O性能,通过内存缓冲区减少对硬件访问。`BufferedInputStream`和`BufferedOutputStream`用于字节流,缓存数据批量读写。`BufferedReader`和`BufferedWriter`处理字符流,支持按行操作。使用后务必关闭流。
9 3
|
4天前
|
存储 缓存 Java
并发编程-Java内存模型到底是什么
并发编程-Java内存模型到底是什么
|
8天前
|
存储 分布式计算 监控
Java一分钟之-Hazelcast:内存数据网格
【6月更文挑战第17天】**Hazelcast是开源的内存数据网格(IMDG),加速分布式环境中的数据访问,提供内存存储、分布式计算、线性扩展及高可用性。常见挑战包括内存管理、网络分区和数据分布不均。通过配置内存限制、优化网络和分区策略可避免问题。示例展示如何创建Hazelcast实例并使用分布式Map。使用Hazelcast提升性能和扩展性,关键在于理解和调优。**
22 1
|
13天前
|
存储 缓存 Java
Java内存模型
Java内存模型
12 0
|
2天前
|
缓存 Java 编译器
Java内存模型深度解析
【6月更文挑战第22天】在探索Java内存模型的迷宫中,我们不仅需要理解其结构,还要揭开它运作的神秘面纱。本文将深入挖掘Java内存模型的核心概念,从硬件架构出发,到Java内存模型的设计哲学,再到并发编程中的实际应用,我们将一步步解码Java内存模型的奥秘。
|
1天前
|
存储 Java C++
Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为几个不同的区域
【6月更文挑战第24天】Java JVM管理内存分7区:程序计数器记录线程执行位置;虚拟机栈处理方法调用,每个线程有独立栈;本地方法栈服务native方法;Java堆存储所有对象实例,垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息;运行时常量池存储常量;直接内存不属于JVM规范,通过`java.nio`手动管理,不受GC直接影响。
11 5
|
4天前
|
Java 机器人 数据库连接
Java中的内存泄漏问题解析与应对
Java中的内存泄漏问题解析与应对
|
1天前
|
算法 Java
垃圾回收机制(Garbage Collection,GC)是Java语言的一个重要特性,它自动管理程序运行过程中不再使用的内存空间。
【6月更文挑战第24天】Java的GC自动回收不再使用的内存,关注堆中的对象。通过标记-清除、复制、压缩和分代等算法识别无用对象。GC分为Minor、Major和Full类型,针对年轻代、老年代或整个堆进行回收。性能优化涉及算法选择和参数调整。
13 3
|
1天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
10 2
|
5天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
17 4