吃透Java内存模型 - 先搞懂内存架构
计算机内存架构
讲Java内存模型之前,还是要帮大家撸一下计算机内存架构知识。因为这个对于理解Java内存模型的设计原理很重要。对于计算机专业出身或者研究过计算机 操作系统原理的同学应该清楚。计算机内存架构大概是这样的
现代计算机内存架构是运行在操作系统内的内存管理机制,早期的计算机压根没有操作系统,内存管理也比较简单,一个计算机基本上只运行一个程序。而操作系统出现后才有了我们熟悉的CPU,寄存器,缓存等概念。我们先来回顾下现代计算机内存架构的几个模块。
- 多核CPU:一个现代计算机可以有多个CPU。CPU还可以有多核。根据CPU分片原理,一个CPU在某一时刻运行一个线程,那么多个CPU就允许程序多个线程在不同的CPU上同时(并发)执行。
- CPU寄存器:每个CPU都包含一系列的寄存器,由于CPU访问寄存器的速度远大于主存。所以寄存器通常用来暂存计算数据,和运算结果,从而极大地提高运算效率。
- CPU缓存:CPU缓存的加入是为了解决计算机的存储设备与处理器的运算速度之间有着数量级的差距的问题,将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。
- 内存:一个计算机还包含一个主存。所有的CPU都可以访问主存,就是说程序都可以从主存申请内存执行运算。
那么计算的内存架构和java内存模型有什么关系呢。先抛java内存模型不讲。假如我们现在不是在开发java程序。而是用另外的语言开发一个程序,考虑到计算机的内存架构是不是有什么内存使用方面要解决的问题呢。答案是肯定的。由于现代计算机允许程序(我们称之为进程)开启并运行多个线程。而线程间通过共享主存可以实现线程间的通信,所谓线程间通信是指,线程A中有一段运行逻辑依赖线程B的输出,那么线程B可以通过更新主存告诉线程A我做完了,你拿去用吧。这样他们之间就实现了一致隐式的通信。
看到这里大家应该感觉到一股熟悉的味道了。运行在计算机内存架构上的程序不得不解决多线程之间访问内存可能带来的数据不一致的问题。Java程序也不例外。所以Java内存模型就是规范了Java虚拟机与计算机内存如何协同工作,而且抽象了各种硬件和操作系统的内存使用的差异,这也是Java程序为什么能跨平台的原因。
Java内存模型的抽象
相对于计算机内存架构,程序的所有数据都在主存中保存,JVM把进程申请到的内存从逻辑上划分了一些区域。并且控制了这些内存区域对于java程序的可见性。在java程序中所有实例对象,静态域,和数组元素都存储在叫堆的内存区域内,而局部变量,方法参数和异常处理参数都存储在叫栈的内存区域。堆对于线程之间是共享的,而栈对于线程之间是独立的不可分享的。这样抽象就类似于主存和本地内存的概念了,堆就相当于主存,而栈就相当于本地内存。那么java内存模型的抽象架构图就像下面这样。是不是跟计算机内存架构很像。
在Java内存模型中,假如线程A和线程B都要使用一个共享变量,两个线程要互相通信就必须经历下面几个过程
- 线程A从主存中读取共享变量并更新到A的本地内存的共享变量副本中
- 线程A更新本地变量副本中共享变量的值
- 线程A把更新后的共享变量副本值从本地内存刷新到主存中
- 线程B从主存中读取最新的共享变量的值到自己的本地内存中
这个过程看起来是多么完美,然而实际运行起来可能不是我们想的那样,因为java进程也是运行在操作系统上的,java的线程也逃不了操作系统CPU时间切片带来的副作用。也就是说如果没有控制的话上面的流程执行顺序就可能不是1,2,3,4 而是1,4,2,3或者1,2,4,3。 总归是有大概率出现数据不一致的情况的。Java内存模型之所以称之为内存架构的一层抽象就是他屏蔽了硬件层的内存处理,编程人员无需介入操作系统的内存管理而可以通过java的程序就能控制线程对内存的使用。