1.栈中存储
- 每个线程都有自己的栈,栈中最小数据单元为栈帧(stack frame)
- 在这个线程上正在执行的每一个方法都对应一个栈帧
- 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
2.栈的运行原理
- JVM直接对栈的操作只有两个,就是对栈帧的压栈和出栈,遵循先进后出、后进先出原则
- 在一个活动的线程中,一个时间点上,只有一个活动的栈帧,即只有当前在执行方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧对应得方法叫当前方法(Current Method),定义这个方法的类叫做当前类(Current Class)
- 执行引擎运行的所有字节码指令只针对当前栈进行操作
- 如果该方法调用了其他方法,对应的新栈帧会被创建出来,放在栈的顶端,称为新的当前栈
- 不同线程中的栈帧不存在互相引用,即不可能在一个栈帧中引用另一个线程的栈帧
- 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新称为当前栈帧
package com.pingan;
public class StackFrameTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
StackFrameTest stackFrameTest = new StackFrameTest();
stackFrameTest.method1();
}
public void method1()
{
System.out.println("method1 开始执行");
method2();
System.out.println("method1 结束");
}
public int method2()
{
int i =10;
System.out.println("method2 开始执行");
method3();
System.out.println("method2 结束");
return i;
}
public int method3()
{
int i =10;
System.out.println("method3开始执行");
System.out.println("method3 结束");
return i;
}
}
输出:
method1 开始执行
method2 开始执行
method3开始执行
method3 结束
method2 结束
method1 结束
- java方法有两种返回函数的方式,一个正常的函件返回,使用return指令,另外一种是抛出异常,不管哪种方式,都是导致栈帧被弹出
3.栈帧的内部结构
-
局部变量表(Local Variables)
1)定义为一个数字数组,主要用于存储方法参数和定义在方法体的局部变量,这个数据类型包括各类基本数据类型、对象引用(reference)以及returnAddress类型 2)由于局部变量表在栈中,属于线程私有数据,因此不存在线程安全问题 3)局部变量表所需的容量大小在编译时期已确认,并保存在方法的Code属性maxinum local variables数据项中,在方法运行期间是不会改变局部变量表的大小 4)方法嵌套调用次数由栈的大小决定,一般来说,栈越大,方法嵌套调用越多,对于一个函数而言,参与的局部变量变多,使得局部变量表膨胀,他的栈帧就越大,已满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套的调用次数就会减少 5)局部变量表的变量只在当前方法调用中有效,在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当方法调用结束后,随着方法栈帧的销毁,局部变量表也随之销毁 6)slot: a)参数值存放在局部变量表数组为index0开始,-1索引结束,局部变量表的最基本的存储单元为slot(变量槽),局部变量表中,32位以内的占用一个slot(returnAddress也占用一个),64为类型的占用两个(long和double) b)jvm会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值 c)当每一个实例方法被调用时候,他的方法参数和方法体内定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上 d)如果需要方位局部变量表中的一个64位的局部变量是,只需要使用前一个索引即可 f)如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存在在index为0的slot出,其他的参数按照参数表顺序继续排列(为什么静态方法this变量不存在,因为this不存在当前的局部变量表中)
slot重复利用代码
public void test1()
{
int a =1;
{
int b =2;
b = a +1;
}
//变量c会占用之前销毁的b的槽位
int c =3;
}
变量分类 : 按照数据类型分类: 基本数据类型和引用数据类型
按照在类声明的位置分: 1)成员变量 :在使用时都初始化
类变量 (static修饰的就是类变量) linking 的prepare阶段已经默认赋值,在Initialize中显示赋值
实例变量:随着对象的创建,在堆空间分配实例变量赋值,并进行默认赋值
2)局部变量:在使用前必须显示赋值,否则编译不通过
public void test1() { int a; System.out.println(a);//变量未初始化,报错 }
- 操作数栈(openrand stack)
1)在方法执行中,根据字节码指令,往栈中写入或者提取数据,即入栈(push)和出栈(pop)
2)主要用于保存计算机的中间结果,同时作为计算过程中变量的临时存储空间
3)操作数栈就是jvm执行引擎的一个工作区,当一个方法开始执行时,一个新的栈帧也会被创建出来,这个方法的操作数栈时空的
4)每个操作数栈都有明确的栈深度来储存数值,其所需要的的大小在编译时定义好了,保存在方法的Code属性里,为MAX_stack
5)栈中的任何一个元素都可以是java的任意数据类型 32位占一个栈深度单位
64两个
6)操作数栈并非采用索引的访问方式,而是通过入栈和出栈来操作一次数据访问
public void add()
{
//byte sort char boolean :默认都以int类型保存
byte i = 15;
int j =8;
int k = i+j;
}
对应的字节码:
0 bipush 15 //0为寄存器 bipush 入操作数栈
2 istore_1 // 15存放到局部变量表
3 bipush 8 // 入操作数栈
5 istore_2
6 iload_1
7 iload_2
8 iadd //出栈
9 istore_3 //存放到局部变量表中
10 return
7)如果调用的方法有返回值,其返回值会被压入当前栈帧的操作数栈中,并更新PC register下一条需执行的字节码指令
public void add()
{
demoAdd();
//byte sort char boolean :默认都以int类型保存
byte i = 15;
int j =8;
int k = i+j;
}
0 aload_0 //方法的返回就压入当期的操作数栈了
1 invokevirtual #19 <com/pingan/com/OpenStackTest.demoAdd>
4 pop
5 bipush 15
7 istore_1
8 bipush 8
10 istore_2
11 iload_1
12 iload_2
13 iadd
14 istore_3
15 return
8)java的虚拟机栈的解释引擎是基于栈的执行引擎,其中栈指的就是操作数栈
- 动态链接(dynamic stack)指向运行是常量池的方法引用
1)每个栈帧内部都包含一个指向运行时常量池中该帧所属方法引用。包含这个引用的目的就是为了支持当前代码能够实现动态链接,比如invokedynamic指令
2)java源文件编译成字节码文件中时,所有的变量和方法引用都作为符号引用(Symoblic Reference)保存在class的文件的常量池中。比如:描述一个方法调用另外一个方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是将这些符号引用转换为调用方法的直接引用
3)静态链接是在class文件在jvm加载时,如果被调用的方法在编译期可知,且在运行期保持不变时,这种情况下将该调用的方法的符号引用转换成直接引用就叫链接
4)绑定:是一个字段、方法或者类在符号引号替换成直接引用的过程,仅仅发生一次
a)早期绑定:目标方法在编译期可知
b)晚期绑定:目标方法在编译期不可知
5)虚方法和非虚方法
a)如果在编译期就确定具体的调用版本,在运行去不可变,就叫非虚方法
静态方法、私有方法、final方法、实例构造器、父类方法都叫非虚方法
其他就是虚方法
指令 invokestatic invokespecial 调用虚方法 invokevirtual 调用虚方法 invokespecial调用方法接口
invokedynamic 动态解析需要调用的方法 非虚方法 - 方法返回地址(return address)方法正常退出或者异常退出的定义
1)存放调用该方法的pc寄存器的值
2)方法的两种方式返回:正常返回return、出现未处理的异常非正常退出
3)无论通过哪种方式退出,都返回该方法被调用的位置,方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条地址,而异常退出的,返回地址通过异常表确定,栈帧不会保存这部分信息 - 一些附件信息(可以不了解)
虚拟机栈面试题
- 举例栈溢出的情况
(stackoverflowError) 通过-Xss 设置栈大小;OOM - 调整栈大小,就能保证不溢出吗
不能, - 分配栈越大越好吗
- 垃圾回收是否涉及虚拟机栈
不涉及 - 方法定义的局部变量表是否线程安全
具体问题具体分析,一般在内部产生对象内部消亡就是线程安全