1. 内存划分(并非JMM)
两个区域: (1)线程独享:JVM Stacks(虚拟机栈) 栈帧 局部变量表
操作数栈
动态链接
方法的返回地址
Native Methods Stacks(本地方法栈)
Program Count register(程序计数器)
(2)线程共享: Method Area(方法区) Runtime Constant Pool(常量池)
Heap(堆)
2. 五大内存区域简介
2.1 程序计数器
(1)程序计数器是一块很小的内存空间,他是线程私有的,可以认为当前线程的行号指示器
(2)程序计数器记录虚拟机字节码指令的地址(运行native代码时,程序计数器内容为空)
2.2 Java栈(虚拟机栈)
(1)栈数据结构为我们理解的stack结构(先进后出)
(2)栈帧数据描述的就是Java方法执行的内存模型
(3)栈帧:局部变量表、操作栈、动态链接和方法出口等信息
* 注意:栈帧的数据结构大小,在编译时确定,不受运行时数据影响
* 涉及到两个异常:1.StackOverflowError栈深度大于虚拟机允许的最大深度
2.OutOfMemory虚拟机栈是可以动态扩展的,当无法申请到足够的空间时抛出
*JVM栈深度并非固定值,栈帧越大栈深度越小,所以JVM分配的栈内存空间上限可能是固定的
* 平时说的栈一般是指局部变量表部分,但实际Java栈并非如此
*栈帧本质上是代表一个方法体,javap字节码方法体的code:stack(操作栈深度并非操作数栈);locals(操作数栈大小);args_size(参数个数,默认加一个this)
2.3 本地方法栈
2.4 堆
(1)堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制
2.5 方法区
(1)方法区和堆一样是线程共享的,为了区分,被称为非堆内存
(2)在老版本的jdk中,方法区也被称为永久代,不涉及垃圾回收,在JDK7中逐步去除永久代
(3)String.interns()方法,如果常量中有这个string则返回这个常量地址
3. 对象的内存结构(堆)
(1)Markword(对象头)-32位系统占8字节;64位系统16字节
*对象头会标记当前对象的四个锁状态(互斥):无锁、偏向锁、轻量锁和重量锁
*无锁:代码中无同步块的内存对象
*偏向锁:只有一个线程进入临界区
*轻量锁:多个线程交替进入临界区(自旋:后进入线程自循环,轮训等待)
*重量锁:1.自旋超时;2.多线程同时进入竞争;(等待线程挂起,直到使用中线程释放对象并notify)
(2)实例数据
(3)对齐填充
4. 并发的内存模型(JMM)
线程1-->线程1工作内存-->load/save动作-->主内存
线程2-->线程2工作内存-->
......
线程n-->线程n工作内存-->
(1)每一个线程都有一个自己的工作内存(类似于CPU的高速缓存),工作内存保存了该内存使用的变量的主存(常量区,堆内存对象)的拷贝
(2)主内存于工作内存数据交互由以下8个原子操作来完成:
*lock (锁定):作用于主内存,标记变量不可重入状态
*read (读取):作用于主内存,拷贝至工作内存
*load (载入):作用于工作内存,把read内容放入工作内存副本中
*use (使用):作用于工作内存,执行引擎遇到使用这个变量的指令时,将会执行这个动作
*assign (赋值):作用于工作内存,执行引擎遇到赋值给这个变量的指令时,执行这个动作
*store (存储):作用于工作内存,把工作内存中的变量传送给主内存
*write (写入):作用于主内存,把传回的变量值存放到主存对应的字段中
*unlock (解锁):作用于主内存,释放锁
(3)原子操作约束
1).read&load、store&write成对出现,不可单独出现,原子操作中间可以可以插入其他指令
2).工作内存中被assign操作过的变量,不可丢弃,必须存储回主内存
3).没发生过assign的变量,不允许存储回主存
4).工作内存只允许从主存read&load变量,不能自行创建
5).lock&unlock必须成对出现(可重复加锁,但必须全部解锁才能释放)
6).对一个变量执行unlock操作前,必须把此变量存储回主内存
(4)volatile约束符
1).可见性,赋值+存储+写入这三个步骤对于volatile描述对象来说是一个原子操作,赋值其他线程立即可见
2).禁止指令重排
3).运算过程非原子化的(i++),还需加同步锁来确保原子性