0 相关源码
内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行
JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行
不同的JVM对于内存的划分方式和管理机制存在着部分差异
结合JVM虚拟机规范,来探讨经典的JVM内存布局
1 Program Counter Register (程序计数寄存器)
Register 的命名源于CPU的寄存器,CPU只有把数据装载到寄存器才能够运行
寄存器存储指令相关的现场信息,由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。这样必然导致经常中断或恢复,如何保证分毫无差呢?
每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常。
1.1. 定义
程序计数器是一块较小的内存空间,可看作当前线程正在执行的字节码的行号指示器
如果当前线程正在执行的是
- Java方法
计数器记录的就是当前线程正在执行的字节码指令的地址 - 本地方法
那么程序计数器值为undefined
1.2. 作用
程序计数器有两个作用
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
1.3. 特点
一块较小的内存空间
线程私有。每条线程都有一个独立的程序计数器。
是唯一一个不会出现OOM的内存区域。
生命周期随着线程的创建而创建,随着线程的结束而死亡。
2. Java虚拟机栈(JVM Stack)
2.1. 定义
相对于基于寄存器的运行环境来说,JVM是基于栈结构的运行环境
栈结构移植性更好,可控性更强
JVM中的虚拟机栈是描述Java方法执行的内存区域,它是线程私有的
栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程
在活动线程中,只有位于栈顶的帧才是有效的,称为当前栈帧
正在执行的方法称为当前方法
栈帧是方法运行的基本结构
在执行引擎运行时,所有指令都只能针对当前栈帧进行操作
StackOverflowError
表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中
JVM能够横扫千军,虚拟机栈就是它的心腹大将,当前方法的栈帧,都是正在战斗的战场,其中的操作栈是参与战斗的士兵
虚拟机栈通过压/出栈的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上
在执行的过程中,如果出现异常,会进行异常回溯,返回地址通过异常处理表确定
栈帧在整个JVM体系中的地位颇高,包括局部变量表、操作栈、动态连接、方法返回地址等
- 局部变量表
存放方法参数和局部变量
相对于类属性变量的准备阶段和初始化阶段来说,局部变量没有准备阶段,必须显式初始化
如果是非静态方法,则在index[0]位置上存储的是方法所属对象的实例引用,随后存储的是参数和局部变量
字节码指令中的STORE指令就是将操作栈中计算完成的局部变量写回局部变量表的存储空间内 - 操作栈
操作栈是一个初始状态为空的桶式结构栈
在方法执行过程中,会有各种指令往栈中写入和提取信息
JVM的执行引擎是基于栈的执行引擎,其中的栈指的就是操作栈
字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的stack属性中
下面用一段简单的代码说明操作栈与局部变量表的交互
- 详细的字节码操作顺序如下:
第1处说明:局部变量表就像个中药柜,里面有很多抽屉,依次编号为0, 1, 2,3,.,. n