Java内存模型的理解

简介: 一.导读关于内存模型和Volatile这块知识点,市面上已经有很多书籍对这块有深入的介绍,今天主要从自己的角度跟大家聊聊这部分内容,希望从不同的视角分析,能给你带来更大的收获。二.内存模型Java内存模型是Java Memory Model的缩写,又简称为JMM,是一个抽象的概念。
一.导读
关于内存模型和Volatile这块知识点,市面上已经有很多书籍对这块有深入的介绍,今天主要从自己的角度跟大家聊聊这部分内容,希望从不同的视角分析,能给你带来更大的收获。
二.内存模型

Java内存模型是Java Memory Model的缩写,又简称为JMM,是一个抽象的概念。Java内存模型的存在主要是用来屏蔽不同硬件平台访问内存的差异。使它们让Java程序在不同的平台下访问内存达到一致的效果。在JVM内部,我们姑且分为堆和栈两部分。当线程创建的时候,JVM会为其创建一个工作内存来存储线程的私有数据,线程对变量的操作都会先从主内存拷贝一份到自己的工作内存当中,进行一系列的运算,然后再将运算结果更新到主内存当中,不能直接对主内存进行操作。线程间的通信(Thread-A和Thread-B), 必须通过主内存完成,它们之间是无法直接访问对方的工作内存。内存模型与系统内存架构关系如下:

94badb7ad922e09f89bddc57d743ae3d12bccee8

通过上图,我们对Java内存模型的工作流程有了一个大致的了解。学过JVM原理的同学可能对Java内存模型跟JVM运行时的数据区搞混,在JVM里内存可能又细分成方法区、虚拟机栈、本地方法栈、程序计数器和堆这五部分,其实它们本质上没什么区别,只是从不同维度上去划分,就像文章开头说的那样,JMM是一个抽象的概念。下面我们再来聊聊,经常听到的并发过程中遇到的几个要处理的特性,原子性、有序性和可见性。
1.原子性
Java中的原子性指的是对基本数据类型的读取和赋值操作是原子操作,这个操作是不可中断和分割,要么全部执行,要么全部不执行。咋一看数据库中的事务还挺像。但值得我们注意的是32位的JVM平台对Long和double两种数据类型的读写不是原子操作,这个我们很容易理解,因为32位的平台,每次读写是32位的存储单元,Long和double占64位,如果是多个线程读写,一个线程读完前32位存储单元,刚好另一个线程读取后32位存储单元,这违背了原子的特性。不过JVM的不断完善,估计这个问题我们可以忽略不计。
2.可见性
可见性指的是多个线程同时访问一个变量的时候,如果一个线程对变量进行了修改,其它的线程可以看到这个变量的改变。当然对于串行的程序来说,这个可见性就可以忽略了,因为任何一个操作修改变量的值,后续的操作都能看到这个变量在变化。对于可见性,Java提供了volatile关键字来保证可见性。关于volatile关键字的讲解,请参照《Java内存模型的理解》一文。
3.有序性
有序性顾名思义就是按照顺序执行,也就是按照代码先后顺序进行执行。
happens-before
关于有序性,除了通过volatile关键字来保证一定"有序性"外,我们还可以用Lock和synchronized来保证有序性,当然Java内存模型本身也可以做到一定"有序性",这就是大家老生常谈的happens-before原则。两个操作,如果如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。举个例子咱们理解下

int x=0; // Thread-A 
int y=x; // Thread-B

问题:这里面咱们猜想一下,Thread-A和Thread-B执行后,y一定等于0么?
解答:如果线程Thread-A的操作(int x=0)happens-before线程Thread-B的操作(int y=x),那么y=0,如果Thread-A和Thread-B不满足happens-before原则,那么y不一定等于0。
关于happens-before定义和规则,我这里直接摘录《深入理解Java虚拟机》
happens-before原则定义:
1.如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2.两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
happens-before原则规则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
重排序
Java程序在执行的时候,为了提高自身性能并且遵循as-if-serial语义,处理器和编译器会对指令进行重排序,不会对存在数据依赖关系的操作进行重排序,并且单线程环境下不能改变程序运行的结果才能进行重排序。
举个例子:

int x=5; // X 
int y=8; // Y
int z=x+y; // Z

关于上面的代码,存在这样关系:X和Z之间存在数据依赖关系,同时Y和Z之间也存在数据依赖关系。为了得到正确的结果,执行指令序列时,Z不能重排序到X和Y的前面。但是X和Y之间并没有数据依赖关系,根据我们上面讲的,没有数据依赖关系处理器和编译器会对指令进行重排序,所以X和Y的执行顺序会被重新调整下面两种情况:

Y-》X-》Z 结果为:13
X-》Y-》Z 结果为:13

上面的代码如果是多线程的情况下进行重排序,会影响程序的运行结果,所以才引发出多线程高并发下数据不一致一系列问题。这里有引出了另一个概念内存屏障。
内存屏障
内存屏障的出现主要是禁止处理器的重排序,并强制把写缓冲区中的脏数据写回主内存,从而让程序按我们预想的流程去执行。内存屏障有些材料又叫内存栅栏,是一个CPU指令,volatile就是基于内存屏障实现的。
内存屏障分Load Barrier(读屏障)和Store Barrier(写屏障)两种。
对于读屏障,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从主内存加载数据;
对于写屏障,在指令后插入Store Barrier,可以让写入缓存中的最新数据更新写入主内存,让其他线程可见。
对于Load和Store实际又分为以下四种
LoadLoad屏障
序列: Load1;Loadload;Load2
说明: 确保Load1所要读入的数据能够在被Load2和后续的load指令访问前读入。
StoreStore屏障
序列: Store1;StoreStore;Store2
说明: 确保Store1的数据在Store2以及后续Store指令操作相关数据之前对其它处理器可见。
LoadStore屏障
序列: Load1;LoadStore;Store2
说明: 确保Load1的数据在Store2和后续Store指令被刷新之前读取。
StoreLoad屏障
序列: Store1;StoreLoad;Load2
说明: 确保Store1的数据在被Load2和后续的Load指令读取之前对其他处理器可见。
以上四种屏障详情访问: http://ifeve.com/jmm-cookbook-mb/
三.参考文献
1.Java并发编程的艺术
2.深入理解Java虚拟机
3.揭秘Java虚拟机-JVM设计原理与实现

个人博客原文:https://www.xiangquba.cn/2018/02/28/java-memory-model/

目录
相关文章
|
2月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
197 3
|
3月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
17天前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
36 4
|
21天前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
2月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
373 17
|
6月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
203 0
|
3月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
156 1
|
3月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。
|
4月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
205 0