美文网首页
JVM内存结构和Java内存模型

JVM内存结构和Java内存模型

作者: 浪够_ | 来源:发表于2020-05-19 11:08 被阅读0次

    说到Java内存模型,许多文章就以栈、堆(heap)、方法区这些名词作为解释,但它们是两个完全不同的概念,或许将JVM内存结构和Java内存模型都解释为栈、堆,是因为程序员最关注的、与对象内存分配关系最密切的区域是“堆”和“栈”,“方法区”这些内存结构吧,姑且这么解释。但想深入理解JVM,就必须清楚JVM内存结构和Java内存模型分别是什么。

    JVM内存结构

    image

    JVM的内存结构大概分为:

    • Java虚拟机栈(Java Stack):线程私有。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧 [1] (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

    • 堆(Heap): Java堆是被所有线程共享的一块内存区域。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在堆上分配。垃圾收集器主要管理的内存区域。

    • 方法区:线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据

    • 本地方法栈(Native Method Stack):线程私有。)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。

    Java 内存模型

    Java线程之间的通信由Java内存模型(本文简称为JMM)控制,它定义了程序中各种变量的访问规则。Java内存模型规定了所有的变量(此处的变量与Java编程中所说的变量有所区 别,它包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,因为后者是线程私有的 ,不会被共享,自然就不会存在竞争问题) 都存储在主内存(Main Memory)中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据 。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图122所示。


    image.png

    这里所讲的主内存、工作内存与所讲的Java内存区域中的Java堆、栈、方法区等并不是同一 个层次的对内存的划分,这两者基本上是没有任何关系的。如果两者一定要勉强对应起来,那么从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分 ,而工作内存 则对应于虚拟机栈中的部分区域。从更基础的层次上说,主内存直接对应于物理硬件的内存,而为了 获取更好的运行速度,虚拟机(或者是硬件、操作系统本身的优化措施)可能会让工作内存优先存储 于寄存器和高速缓存中,因为程序运行时主要访问的是工作内存。

    内存间交互操作

    关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从 工作内存同步回主内存这一类的实现细节,Java内存模型中定义了以下8种操作来完成。Java虚拟机实 现时必须保证下面提及的每一种操作都是原子的、不可再分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许有例外)

    • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。

    • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量 才可以被其他线程锁定。

    • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以 便随后的load动作使用。

    • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的 变量副本中。

    • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚 拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

    • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量, 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

    • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随 后的write操作使用。

    • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的 变量中。

    如果要把一个变量从主内存拷贝到工作内存,那就要按顺序执行read和load操作,如果要把变量从 工作内存同步回主内存,就要按顺序执行store和write操作。注意,Java内存模型只要求上述两个操作 必须按顺序执行,但不要求是连续执行。也就是说read与load之间、store与write之间是可插入其他指令 的,如对主内存中的变量a、b进行访问时,一种可能出现的顺序是read a、read b、load b、load a。除此 之外,Java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:

    • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内 存不接受,或者工作内存发起回写了但主内存不接受的情况出现。

    • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回 主内存。

    • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存 中。 ·一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或 assign)的变量,换句话说就是对一个变量实施use、store操作之前,必须先执行assign和load操作。

    • 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执 行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

    • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量 前,需要重新执行load或assign操作以初始化变量的值。

    • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个 被其他线程锁定的变量。

    • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

    这8种内存访问操作以及上述规则限定,再加上稍后会介绍的专门针对volatile的一些特殊规定,就 已经能准确地描述出Java程序中哪些内存访问操作在并发下才是安全的。这种定义相当严谨,但也是 极为烦琐,实践起来更是无比麻烦。可能部分读者阅读到这里已经对多线程开发产生恐惧感了,后来 Java设计团队大概也意识到了这个问题,将Java内存模型的操作简化为read、write、lock和unlock四 种,但这只是语言描述上的等价化简,Java内存模型的基础设计并未改变,即使是这四操作种,对于 普通用户来说阅读使用起来仍然并不方便。不过读者对此无须过分担忧,除了进行虚拟机开发的团队 外,大概没有其他开发人员会以这种方式来思考并发问题,我们只需要理解Java内存模型的定义即可。

    相关文章

      网友评论

          本文标题:JVM内存结构和Java内存模型

          本文链接:https://www.haomeiwen.com/subject/dnainhtx.html