​ 在之前的一些有关并发的文章里面,掺杂着各种JMM有关的知识点,在这里加入JVM的内存统一整理一下。

Java内存区域

​ 在《深入理解JVM》一书中,对Java的内存区域(运行时数据区)进行了比较明确的划分:
Java虚拟机运行时数据区

虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。常说的Java栈指的就是在Java虚拟机栈中的局部变量表部分。
局部变量表部分存储一直的基本数据类型对象引用和returnAddress类型。
在虚拟机栈中可能出现的错误:

线程请求的栈深度大于虚拟机允许的深度,抛出StackOverFlowError异常。
扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。

本地方法栈

本地方法栈为虚拟机使用的Native方法进行服务,其他的基本类似于虚拟机栈。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

Java堆(Java Heap)是Java虚拟机中管理的内存最大的一块,并且堆被所有的线程共享。堆会存放对象实例,在JVM规范中的描述是:所有的对象实例以及数组都要在堆上分配。
Java堆是垃圾收集器管理的主要区域,所以也被称为”GC堆”。还可能存在多种更为细致的划分,目的是更好的回收内存,或者更快的分配内存。
在JVM规范中,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的。
在堆中可能出现的错误

如果在堆中没有内存完成实例分配,并且堆再也无法扩展时,将会抛出OutOfMemoryError异常。

方法区

方法区跟Java堆一样,是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也被称之为Non-Heap(非堆),用以跟Java堆的区分。
在方法区中存在运行时常量池,用来存放编译期生成的各种字面量和符号引用以及存储翻译出来的直接引用,并且运行期常量池是能够在运行过程中进行添加的,比如String类的intern()方法。
常量池和运行期常量池辨析

在Class文件中,除了有类的版本、字段、方法、借口等描述信息,还有常量池,常量池存放编译期生成的各种字面量和符号引用,这一部分内容会在类加载后刷入到方法区的运行时常量池中存储。
运行期常量池是方法区的一部分,并且能够在运行期间进行添加。

方法区可能出现的错误

当方法区无法满足内存分配时,将会抛出OutOfMemoryError异常。

Java内存模型

​ 在Java中,采用的线程通信方式是共享内存模型,所以Java内存模型(Java Memory Model)的实现是通过共享内存的方法进行实现的。Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
Java的内存模型规定了所有的变量都储存在主内存, 每条线程还有自己的工作过内存,线程的工作内存保存了线程中使用到的主内存副本拷贝,线程的操作是对工作内存的操作,不允许直接操作主内存。并且线程之间的工作内存是互相隔离的,线程间的传递需要通过主内存,这也就是共享内存模型的内容。
线程、工作内存、主内存三者的交互关系
除去基本的内存模型,JMM保证了可见性、原子性、有序性。

可见性的概念也就是内存可见性,JMM对于可见性的支持是采用了volatile关键字或者锁来保证可见性。
原子性的概念也就是在执行过程中,一个操作是不能够中断的,这之间存在线程之间的共享变量读取写入的问题。在JMM中,采用了synchronized来保证并发过程的安全性和操作的原子性。
有序性指的是在指令执行过程中的指令重排序现象,JMM采用了volatile、synchronized、final分别做到了不同的保证,来确保在多线程环境过程中的语义有序性,例如实现happens-before规则和as-if-serial语义。

​ 上述提到的JMM确保的规则,在其他文章中都有提及,这里不再复述。