Java内存模型

简介:

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

关于上面的代码,存在这样关系: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设计原理与实现

目录
相关文章
|
13天前
|
存储 Java 编译器
Java内存区域详解
Java内存区域详解
29 0
Java内存区域详解
|
23天前
|
缓存 算法 Java
Java内存管理与调优:释放应用潜能的关键
【4月更文挑战第2天】Java内存管理关乎性能与稳定性。理解JVM内存结构,如堆和栈,是优化基础。内存泄漏是常见问题,需谨慎管理对象生命周期,并使用工具如VisualVM检测。有效字符串处理、选择合适数据结构和算法能提升效率。垃圾回收自动回收内存,但策略调整影响性能,如选择不同类型的垃圾回收器。其他优化包括调整堆大小、使用对象池和缓存。掌握这些技巧,开发者能优化应用,提升系统性能。
|
1月前
|
监控 Java 数据库连接
解析与预防:Java中的内存泄漏问题
解析与预防:Java中的内存泄漏问题
|
20天前
|
缓存 安全 Java
Java并发编程进阶:深入理解Java内存模型
【4月更文挑战第6天】Java内存模型(JMM)是多线程编程的关键,定义了线程间共享变量读写的规则,确保数据一致性和可见性。主要包括原子性、可见性和有序性三大特性。Happens-Before原则规定操作顺序,内存屏障和锁则保障这些原则的实施。理解JMM和相关机制对于编写线程安全、高性能的Java并发程序至关重要。
|
27天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
76 0
|
1天前
|
Java 程序员 数据库连接
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
|
2天前
|
存储 安全 Java
滚雪球学Java(19):JavaSE中的内存管理:你所不知道的秘密
【4月更文挑战第8天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 4
滚雪球学Java(19):JavaSE中的内存管理:你所不知道的秘密
|
10天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
18天前
|
存储 缓存 安全
【企业级理解】高效并发之Java内存模型
【企业级理解】高效并发之Java内存模型
|
24天前
|
Java
java中jar启动设置内存大小java -jar 设置堆栈内存大小
java中jar启动设置内存大小java -jar 设置堆栈内存大小
12 1