2.2 运行时数据区域
2.2.1 程序计数器(program counter register)
JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟 作用:PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令
程序计数器是一块较小的内存空间(只存储指令地址),是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。
可看做是当前线程所执行的行号指示器。用来指示线程选取下一条需要执行的字节码。各条线程之间计数器互相不影响,独立存储,“线程私有”的内存,生命周期与线程的生命周期保持一致。
- 线程在执行一个java方法,该计数器记录的是正在执行的虚拟机字节码指令地址
- 执行Native方法时,该计数器值为Undefined
实例: 对于如下代码
public class Test {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
String s = "abc";
System.out.println(i);
System.out.println(k);
}
}
复制代码
使用javap -v Test.class
反编译之后,可以得到如下内容
两个常见问题:
-
使用PC寄存器存储字节码指令地址有什么用?为什么使用PC寄存器记录当前线程的执行地址?
CPU需要不停地切换各个线程,切换回来后,需要知道从哪里接着开始
JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
-
PC寄存器为什么设定为线程私有
为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的就是每个线程分配一个
2.2.2 Java虚拟机栈
1、栈是运行时单位,而堆是存储时单位
栈解决程序的运行问题,即程序如何运行,或者说如何处理数据
堆解决数据存储问题,即数据怎么放,放在哪
线程私有的,生命周期与线程相同
2、该区域会出现两种异常
-
StackoverflowError:线程请求的栈深度大于虚拟机所允许的
/** * PC中使用默认值:count - 9782 * 设置栈大小 -Xss256k : count - 2214 */ public class Test { private static int count = 1; public static void main(String[] args) { System.out.println(count++); main(args); } } 复制代码
-
OutOfMemoryError:大部分虚拟机栈可动态扩展,如果扩展时无法申请到足够的内存
3、栈的存储单位
栈中的数据以栈帧(Stack Frame)格式存在,每个方法对应一个栈帧。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
4、栈的运行原理
对栈的操作:压栈、出栈
在一条线程中,一个时间点上只有一个活动的栈帧:
只有当前正在执行的方法的栈帧是有效的,称为当前栈帧(Current Frame)
与当前栈帧相对应的方法称为当前方法(Current Method)
定义这个方法的类称为当前类(Current Class)
执行引擎运行的所有字节码指令只针对当前栈帧操作
如果该方法调用了其它方法,对应的新的栈帧会被创建,放在栈顶,称为新的当前帧
不能在一个栈帧中引用另一个线程中的栈帧
return指令和抛出异常都会导致栈帧被弹出
5、栈帧的内部结构
局部变量表(Local Variables)
操作数栈(Operand Stack)(或表达式栈)
动态链接(Dynamic Link)(或指向运行时常量池的方法引用)
方法返回地址(Return Address)(或方法正常退出或异常退出的定义)
一些附加信息
6、局部变量表
也称为局部变量数组或本地变量表,javap 反编译字节码文件中的 LocalVariableTable
定义为数字数组,用于存储方法参数和定义在方法体内的局部变量
这些数据类型包括:基本数据类型、对象引用(reference)、returnAddress类型
局部变量表是建立在线程的栈上,不存在数据安全问题
局部变量表所需容量在编译期确定下来
在方法运行期间不会改变表大小
7、关于slot的理解
局部变量表的最基本存储单位是Slot(变量槽)
局部变量表中,32位以内的类型只占一个slot(包括returnAddress类型)
byte、short、char存储前转换成int
boolean也转换成int:0 - false、非0 - true
64位的类型(long、double)占两个slot
当实例方法被调用时,其方法参数和方法体内部定义的局部变量会按照顺序被复制到局部变量表中的每个slot中
如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可
如果当前帧是由构造方法或实例方法创建,this指针存放在index0的slot处
8、slot的重复利用
如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量可能会复用过期局部变量槽位,从而节省资源
9、静态变量和局部变量的对比
类变量两次初始化:
- 准备阶段:对类变量设置零值
- 初始化阶段:赋予代码中定义的初始值
局部变量表不存在初始化,意味着局部变量必须人为初始化
public void test4() {
int i;
System.out.println(i); // Variable 'i' might not have been initialized
}
复制代码
10、操作数栈
在方法执行过程中,根据字节码指令,向栈中写入数据或提取数据,即入栈出栈
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时的存储空间
操作数栈是JVM执行引擎的一个工作区,当一个方法开始执行的时候&