Java内存模型(JMM)
标签(空格分隔): Java 架构 线程/进程
JMM 本身是一种抽象的概念并不真实存在, 它描述的是一组规则或规范, 通过这组规范定义了程序中各个变量(包括实例字段, 静态字段和构成数组对象的元素)的访问方式.
JMM关于同步的规定.
- 线程解锁之前, 必须把共享变量的值刷回主内存.
- 线程加锁之前, 必须要读取主内存的最新值到自己的工作内存.
- 加锁和解锁的对象是同一把锁.
由于JVM运行程序的实体是线程, 每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间), 工作内存是每个线程的私有数据区域, 而Java内存模型中规定所有变量都存储在主内存, 主内存是共享内存区域, 所有的线程都可以访问, 但是线程对变量的操作必须在工作内存(栈空间)中进行, 首先将变量从主内存拷贝到自己的工作内存空间, 然后对变量进行操作, 操作完成后再将变量写会主内存, 不能直接操作主内存中的变量, 各个线程中的工作内存中存储着主内存中的变量拷贝副本, 因此不同的线程无法访问对方的工作内存, 线程之间的通信必须通过主内存来完成.
Java程序的执行过程
Java源代码文件, 经过Java编译器编译之后形成Java字节码文件(.class), 然后到类加载器中和运行时数据区和执行引擎进行交互,得到结果.
Java源代码文件(.java)会被Java编译器编译为字节码文件(.class), 然后由JVM中的类加载器加载各个类的字节码文件, 加载完毕之后, 交给JVM执行引擎执行, 在整个程序执行过程中, JVM会用一段空间来存储程序执行期间需要用到数据和相关信息, 这段空间一般被称为运行时数据区, 也就是我们常说的JVM内存. 因此在Java中我们常常说道内存管理就是针对这一段内存空间进行管理(如何分配和回收空间).
运行时数据区包括哪几个部分
1. 方法区(Method Area)
方法区是各个线程共享的内存区域, 它用于存储已被虚拟机加载的类信息, 常亮, 静态变量, 及时编译器编译后的代码等数据. 当方法区无法满足分配的需求的时候就会抛出OutOfMemoryError异常.
因为是各个线程共享的方法区, 所以其内存储的都是不变的信息如类信息, 常量, 静态变量, 即使编译器编译之后的代码等数据.
2. JVM堆(Java Heap)
Java堆也是属于线程共享的内存区域, 他在虚拟机启动的时候被创建, 是Java虚拟机所管理的内存中的最大的一块, 主要用于存储实例对象, 几乎所有的对象实例都在这里分配内存, 注意Java堆是垃圾收集管理器的主要区域, 因此很多时候也被成为GC堆, 如果在队中没有内存完成分配实例, 并且在堆也无法扩展的时候将会抛出OutofMemoryError异常.
3. 程序计数器(Program Counter Register)
字节码解释器工作的时候, 通过改变这个计数器的值来选取吓一跳需要执行的字节码指令, 分支, 循环, 跳转, 异常处理, 线程回复等基础功能都需要依赖这个计数器来完成.
多线程中, 为了让线程切换之后能够恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 各条线程之间互不影响, 独立存储, 因此这块内存是线程私有的.
4. 虚拟机栈(Java Virtual Machine Stacks)
Java虚拟机也是线程私有的, 他的生命周期和线程相同. 虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行的同时都会创建一个栈帧用于存储局部变量表, 操作数栈, 动态链表, 方法出口等信息. 每一个方法从调用到执行完成的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.
5. 本地方法栈(Native Method Stacks)
本地方法栈属于线程私有的数据区域, 这部分主要和虚拟机用到的Native方法相关, 一般情况下, 我们无需关系此区域.
栈内存: 虚拟机栈, 本地方法栈
本地方法栈: 线程私有的数据区域, 一般和Native方法相关. 非常稳定和安全, 一般情况下不需要考虑这里的问题.
虚拟机栈: 同样线程私有, 生命周期跟随线程. 虚拟机栈用于描述Java方法执行的时候的内存模型: 每个方法在执行的时候, 需要自己单独一个栈帧存储局部变量, 方法出口等, 方法执行需要的信息. 每一个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机中从出栈到入栈的过程.
堆内存
Java堆内存是线程共享的区域, 是GC的主要目标空间, 也被称为GC堆. Java虚拟机下最大的空间, 主要用于存储类的实例对象.
程序计数器
字节码解释器工作的时候, 通过改变这个计数器的值来 选取下一跳需要执行的字节码指令, 分支, 循环, 跳转等功能都需要计数器做位置提示和标记. 因为牵扯到多线程, 每次在线程恢复之后需要重新找到原来的执行位置, 所以程序计数器是线程私有的.
Java源代码文件, 经过Java编译器编译之后形成Java字节码文件(.class), 然后到类加载器中和运行时数据区和执行引擎进行交互,得到结果.
源代码文件经过编译器 编译为字节码文件(.class), 字节码文件可以通过不同平台的JVM解释为该平台的具体指令. 类加载器加载字节码文件到方法区. 进入就绪态之后, 开始分配堆内存和栈内存的空间, 并且程序计数器开始指向需要运行的字节码行, 然后开始执行.