JAVA内存模型和JMM

922 阅读7分钟

内存模型

image.png

方法区

方法区属于线程共享的内存区域,又称Non-Heap(非堆),主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool) 的区域,它主要用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到运行时常量池中,以便后续使用。

Java 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC 堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

虚拟机栈

属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用直结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程。

image.png

本地方法栈

地方法栈属于线程私有的数据区域,这部分主要与虚拟机用到的 Native 方法相关,一般情况下,我们无需关心此区域

程序计数器

属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码信号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

JMM

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

  Java内存模型中规定所有变量都存储在主内存;

  JVM调度的实体是线程,JVM为各个线程创建了私有内存空间(工作内存);

  线程操作要在各自的私有内存空间(工作内存)中,不能操作共享的主内存中的变量,工作内存中存储着主内存中的变量副本拷贝;变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存;(重点)

  JMM是围绕原子性,有序性、可见性展开的。

主内存

主要存储的是Java实例对象,包括成员变量和局部变量,还有共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。

工作内存

主要存储当前方法的所有局部变量(工作内存中存储着主内存中的变量副本拷贝);每个线程只能访问自己的工作内存,即线程中的局部变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的局部变量,当然也包括了字节码行号指示器、相关Native方法的信息;注意由于工作内存中数据是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。由于同时不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

主内存与工作内存存储的数据类型

  根据虚拟机规范,对于一个实例对象中的成员方法而言:

  1.局部变量是基本数据类型(boolean,byte,short,char,int,long,float,double),将直接存储在工作内存(JVM栈的帧栈)中;局部变量是引用类型,变量的引用会存储在工作内存(JVM栈的帧栈)中,而对象实例将存储在主内存(堆)中;

  2.成员变量不管它是基本数据类型或者包装类型(Integer、Double等)还是引用类型,变量引用和实例都会被存储到主内存(堆区);

  3.全局变量(也就是static修饰的变量,也可以称为类变量)存储在主内存中(方法区中);

对于上文提到的在工作内存中存储的数据是分为基本类型和引用类型的,这个目前本人存在疑问,有的人说全部都是引用类型,他做了一个假设,如果在主内存中的这个变量非常大,那么如果是值的拷贝,是不是可能会造成线程安全的问题?所以他认为都是引用。
对于本问题,欢迎大家留言讨论。

内存的实际存储位置

通过对Java内存模型,硬件内存架构、以及Java多线程的实现原理的了解,我们应该已经意识到,多线程的执行最终都会映射到硬件处理器上进行执行。对于硬件内存来说只有寄存器、缓存内存、主内存的概念,并没有工作内存(线程私有数据区域)和主内存(堆内存)之分,也就是说Java内存模型对内存的划分对硬件内存并没有任何影响,因为JMM只是一种抽象的概念,是一组规则,并不实际存在,不管是工作内存的数据还是主内存的数据,对于计算机硬件来说都会存储在计算机主内存中,当然也有可能存储到CPU缓存或者寄存器中,因此总体上来说,Java内存模型和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念划分与真实物理硬件的交叉。

image.png 从上文可知,工作内存和主内存,实际就是存储在CPU寄存器中、CPU缓存中、和RAM中。

内存模型和JMM区别

JMM与Java内存区域的划分是不同的概念层次,更恰当说JMM描述的是一组规则,而后者是实际的区域划分;

  JMM与Java内存区域唯一相似点,都存在共享数据区域和私有数据区域。在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了堆和方法区;而工作内存(线程私有数据区域),从某个程度上讲则应该包括程序计数器、虚拟机栈以及本地方法栈。

  某些地方,我们可能会看见主内存被描述为堆内存,工作内存被称为线程栈,实际上他们表达的都是同一个含义

参考: