文章目录
1.运行时数据区模型
在这个图中,灰色的表示每个线程独有,红色的表示线程共享
2.虚拟机栈的特点
java虚拟机栈
早期也叫java栈
,每个线程在创建时都会创建一个虚拟机栈,如图所示
多个线程所对应的栈组成了我们所说的java栈
,说到栈,就不得不提栈帧
,栈帧
是java虚拟机最基本的执行单元,一个栈帧对应一个方法,栈帧存储了方法的局部变量表,操作数栈,动态链接和方法返回地址等信息,文章后面会一一介绍,虚拟机栈的生命周期和线程一致,栈的特点:
- 快速有效的存储方式,访问速度仅次于pc寄存器
jvm
对栈的操作:入栈出栈,即栈帧的使用和卸载的过程,先进后出,后进先出- 不存在垃圾回收问题
补充:在一个java程序中,在同一时刻,同一线程,只有位于栈顶的栈帧才是有效的,被称为当前栈帧,栈帧的结构:
2.1栈运行原理
- 由于栈是线程独有,所以不同的线程间的栈帧不允许存在相互引用,即不可以在一个栈帧中引用另外一个线程的栈帧
- 当前方法调用了其他方法,那么被调用的方法返回之际,当前栈帧会传回此方法的执行结果给调用它的线程,然后虚拟机会执行出栈操作,丢弃当前栈帧,让前一个栈帧继续充当当前栈帧
- java中有两种返回函数的方式,一是程序正常结束,使用
return
指令(注意,这里的return
是虚拟机内部给每个方法都会加上的关键字,表示这个方法正常结束,和我们显示的写不写return
没有关系),二是抛出异常,两种方式都会导致当前栈帧被弹出
我们通过代码来演示当程序抛出异常会产生什么结果
public class stackTest {
public static void main(String[] args) {
stackTest s = new stackTest();
s.method1();
}
void method1(){
System.out.println("method1 start......");
method2();
System.out.println("method1 end......");
}
void method2(){
System.out.println("method2 start......");
method3();
System.out.println("method2 end......");
//System.out.println(10 / 0);
}
void method3(){
System.out.println("method3 start......");
System.out.println("method3 end......");
}
}
先把程序出现异常的地方注释。看正常结果
method1 start......
method2 start......
method3 start......
method3 end......
method2 end......
method1 end......
然后打印错误的结果:
method1 start......
method2 start......
method3 start......
method3 end......
method2 end......
Exception in thread "main" java.lang.ArithmeticException: / by zero
为什么会出现这种结果呢?首先method2
发生了异常,抛给method1
,但是方法1没有捕获异常,所以程序没有正常结束,我们只需要捕获这个异常就可以使栈帧正常被压入,弹出
void method1(){
System.out.println("method1 start......");
try {
method2();
} catch (Exception e){
e.printStackTrace();
}
System.out.println("method1 end......");
}
输出结果:
method1 start......
method2 start......
method3 start......
method3 end......
method2 end......
method1 end......
java.lang.ArithmeticException: / by zero
2.2局部变量表(local variables table)
局部变量表是一组变量值的存储空间,用来存储方法参数和定义在方法内的局部变量,我们可以这样来理解这个表:一个一维数组,一个方法的调用即实参到形参的传递就是通过局部变量表完成,局部变量表以变量槽(slot) 为最小单位,这个变量槽都能放入一个
- boolean
- int
- float
- char
- short
- byte
- 对象引用reference
- returnAddress
- double
- long
这8种数据类型,其中32位类型的占用一个slot
,64位类型的占用两个slot
,由于局部变量表存在栈中,因此不存在数据安全问题,需要注意的是:局部变量表的大小是在编译器确定下来的并且保存在方法的code属性的maximum local variables
数据项中,运行期间不会改变,我用我的idea
做演示
附上我的代码:
public static void main(String[] args) {
stackTest s = new stackTest();
s.method1();
}
可以和清楚的看见,main
方法中确实只有两个变量,s和args
,长度为2,
追加:关于Slot的理解和jclasslib的使用
请参考关于Slot的理解和jclasslib的使用
文章到这个位置我们就可以解决之前一个问题了:
类变量和实例变量在使用之前都会在类加载阶段赋予默认值,但局部变量无法赋默认值,必须显示赋值,否则编译不通过,其原因就是:局部变量表中存储了这个变量的值,基本数据类型就存值,引用类型就存的是地址,本质上还是值,所以局部变量表不能为空
2.3操作数栈(Operand Stack)
解释:在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)
java虚拟机的解释引擎是基于栈的执行引擎,这里的栈就是操作数栈
说明:文章会持续更新