1.3 运行时数据区
java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机二创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
灰色的为单独线程私有的,红色为多个线程共享的。即:
每个线程:独立包括程序计算器、栈、本地方法栈。
线程间共享:堆、堆外内存(永久代或元空间、代码缓存[JIT编译产物])
注意:一个jvm实例就是一个runtime对象,就是运行时数据区,下面就按照数据区里面的模块逐一分析,详细图例如下:
1.3.1 程序计数器
PC Register 介绍
jvm中的程序计算寄存器中,register的命名起源于CPU的寄存器,寄存器储存指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。它是很小的一块内存空间,存储下一条指令的地址;它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。既没有GC也没有OOM。
Q1:使用PC寄存器存储字节码指令地址有什么用?
答:因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
Q2:PC寄存器为什么会被设定为线程私有?
答:CPU会不停地做任务切换,这样必然导致经常中断或恢复,为了准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
1.3.2 虚拟机栈
- java虚拟机栈(JAVA Virtual Machine Stack),早期也叫java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的java方法调用。是线程私有的。它的生命周期和线程一致,作用是主管java程序的运行它保存方法的局部变量、部分结果,并参与方法的调用和返回。
- 遵循“先进后出”原则,在一个时间点上只会有一个活动的栈帧。即只有当前正在执行的方法栈帧(栈顶栈帧)是有效的,这个栈帧称为当前栈帧(Current Frame),通过-Xss 可以设置栈的最大空间
- 一个方法就是一个栈帧,每个栈帧存储着“局部变量表”、“操作数栈”、“动态链接”、“方法返回地址”、“一些附加信息”
局部变量表
- 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量
- 局部变量表建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
- 容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。运行期间不会改变大小
所有的关键信息都在这张表中
Code:可以看到详细的字节码指令
LineNumberTable:字节码指令行数和java代码行数的对应关系
LocalVariableTable:展示局部变量的信息信息
- 局部变量表最基本的存储单元是slot(变量槽),存放编译期就已经确定的8中基本类型、引用类型、returnaddress 类型 ,32位以内的类型占一个slot,64位的类型(long、double)占2个slot
- 如果当前帧是有构造方法或实例方法创建的,那么对象的引用变量this会存放在index索引为0的位置上,其他的变量按照声明的顺序排列。
- slot有重复利用的问题
- 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
操作数栈
操作数栈在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈或出栈。主要用于保存计算过程中间结果,同时作为计算过程中变量临时的存储空间。
public int testB(){
int i=6;
int j=7;
int k =i+j;
return k;
}
//-----------------------javap 反编译后字节码文件
public int testB();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: bipush 6
2: istore_1
3: bipush 7
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: iload_3
11: ireturn
LineNumberTable:
line 31: 0
line 32: 3
line 33: 6
line 34: 10
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this Lcom/example/jvm/LocalVariableTableTest;
3 9 1 i I
6 6 2 j I
10 2 3 k I
}
我们根据这个方法实际分析一下:
说明:当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令。
- Code:stack=2 locals=4 含义是操作数栈深度2,局部变量表长度为4
- bipush ireturn 是入栈操作,iload是出栈操作
- 0:将6入栈
- 2:将6取出来存储在局部变量表中,位置为1(0 放的是this)
- 3:将7入栈
- 5:将7取出来存储在局部变量表中,位置为2
- 6:从局部变量表位置1中的变量取出来入栈
- 7:从局部变量表位置2中的变量取出来入栈
- 8:执行引擎通过iadd指令进行运算得到结果
- 9:将栈中的结果取出来存储在局部变量表中,位置为3
- 10:从局部变量表位置3中的变量取出来入栈
- 11:返回值 (栈中的值13)