~本文小结:
- 在Java中,并发使用的是共享内存并发模型。
- Java程序在运行的时候,Jvm的内存区域主要分为以下五个部分:程序计数器、虚拟机栈、本地方法栈、方法区、堆;
- 程序计数器:存放下一条Java方法指令所在单元的地址;
- 虚拟机栈:存放方法运行时产生的栈帧,包括局部变量表,操作数栈,动态链接,出口信息等信息;
- 本地方法栈:给Native方法使用的栈空间;
- 方法区:线程共享,存放类信息,静态变量,静态方法等静态的内容;
- Java堆(Heap):线程共享,存放对象实例;
- JMM内存模型:一套jvm规范定义的规则;用与屏蔽掉java程序在不同平台中对内存的访问的差异,以实现不同平台上的内存访问一致性;
- JMM规定,Java线程和工作内存进行交互,工作内存和主内存的交互有JMM控制;
在Java中,并发使用的是共享内存并发模型。
下面将介绍Java内存模型的抽象结构以及Java中内存不可见问题出现的原因。
1、Java运行时内存区域的划分
Jvm的内存区域主要分为以下五个部分:程序计数器、虚拟机栈、本地方法栈、方法区、堆,如下图所示:
(1)程序计数器
- 程序计数器是用于存放下一条指令所在单元的地址的地方。
- 如果线程正在执行的是一个Java方法,这个计数器记录的则是正在执行的虚拟机字节码指令的地址;
- 如果正在执行的是Native方法,这个计数器则为空(undefined)。
占据一块较小的内存空间,可以看做当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
(2)虚拟机栈
线程私有,生命周期和线程相同;
- 描述Java方法执行时的内存模型,每个方法执行时都会创建一个栈帧;
- 用于储存局部变量表,操作数栈,动态链接,方法出口等信息;
栈帧:
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
(3)本地方法栈
为Native方法服务。功能与虚拟机栈一致。
(4)方法区
- 被所有线程共享;
- 储存已经被jvm加载的类信息,常量,静态变量;
- 常量池:字符串常量池
静态变量、静态方法:
没有实例化(创建对象)时也可以直接使用
(5)Java堆(Heap)
- 被所有线程共享;
- 存放对象实例,分配内存;
- 是垃圾回收器管理的主要区域,故又称为“Gc堆”
堆:
通常是一个可以被看做一棵完全二叉树的数组对象。
2、Java内存模型(JMM)
注意,根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取。
所以线程B并不是直接去主内存中读取共享变量的值,而是先在本地内存B中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存B去主内存中读取这个共享变量的新值,并拷贝到本地内存B中,最后线程B再读取本地内存B中的新值。
那么怎么知道这个共享变量的被其他线程更新了呢?这就是JMM的功劳了,也是JMM存在的必要性之一。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。
3、JMM与Java内存区域划分的区别与联系
区别
- JMM是抽象的,他是用来描述一组规则,通过这个规则来控制各个变量的访问方式,围绕原子性、有序性、可见性等展开的。
- Java运行时内存的划分是具体的,是JVM运行Java程序时,必要的内存划分。
联系
- 都存在私有数据区域和共享数据区域。
- JMM中的主内存属于共享数据区域,包含了堆和方法区;
- JMM中的本地内存属于私有数据区域,包含了程序计数器、本地方法栈、虚拟机栈。
学习参考:
《深入浅出Java多线程》