google全球推广,seo链接优化建议,昆明手机网站推荐,wordpress post 插件虚拟机栈
Java虚拟机栈#xff08;Java Virtual Machine Stack#xff09;采用栈的数据结构来管理方法调用中的基本数据#xff0c;先进后出#xff08;First In Last Out#xff09;,每一个方法的调用使用一个栈帧#xff08;Stack Frame#xff09;来保存。
接下来以…虚拟机栈
Java虚拟机栈Java Virtual Machine Stack采用栈的数据结构来管理方法调用中的基本数据先进后出First In Last Out,每一个方法的调用使用一个栈帧Stack Frame来保存。
接下来以这段代码为例
Java
public class MethodDemo { public static void main(String[] args) { study(); }public static void study(){eat();sleep();} public static void eat(){ System.out.println(吃饭); } public static void sleep(){ System.out.println(睡觉); }}main方法执行时会创建main方法的栈帧 接下来执行study方法会创建study方法的栈帧 进入eat方法创建eat方法的栈帧 eat方法执行完之后会弹出它的栈帧 然后调用sleep方法创建sleep方法栈帧 最后study方法结束之后弹出栈帧main方法结束之后弹出main的栈帧
在IDEA中也可以看到对应的栈帧
public class FrameDemo {public static void main(String[] args) {A();}public static void A() {System.out.println(A执行了...);B();}public static void B() {System.out.println(B执行了...);C();}public static void C() {System.out.println(C执行了...);throw new RuntimeException(测试);}
}打上断点debug之后会出现栈帧内容由于上面的代码是方法调方法我们也可以直观得看到如果是递归调用太多次就会造成很多栈帧存储到栈中最终就会造成栈溢出 如果在C方法中发生了异常虚拟机栈就会将此时的栈帧依次弹出栈满足栈的先进后出这也有一个Debug小技巧出现异常时我们要定位问题只需要找到最上面的方法即可。 Java虚拟机栈随着线程的创建而创建而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行每个线程都会包含一个自己的虚拟机栈。如下就有两个线程的虚拟机栈main线程和线程A。 Java虚拟机栈的栈帧中主要包含三方面的内容 局部变量表局部变量表的作用是在运行过程中存放所有的局部变量 操作数栈操作数栈是栈帧中虚拟机在执行指令过程中用来存放临时数据的一块区域 帧数据帧数据主要包含动态链接、方法出口、异常表的引用 局部变量表 局部变量表的作用是在方法执行过程中存放所有的局部变量。局部变量表分为两种一种是字节码文件中的另外一种是栈帧中的也就是保存在内存中。栈帧中的局部变量表是根据字节码文件中的内容生成的。 字节码文件局部变量表
我们先来看下字节码文件中的局部变量表编译成字节码文件时就可以确定局部变量表的内容。
public static void test1(){int i 0;long j 1;
}
test1方法的局部变量表如下 局部变量表中保存了字节码指令生效的偏移量 比如i这个变量它的起始PC是2代表从lconst_1这句指令开始才能使用i长度为3也就是2-4这三句指令都可以使用i。为什么从2才能使用因为0和1这两句字节码指令还在处理int i 0这句赋值语句。j这个变量只有等3指令执行完之后也就是long j 1代码执行完之后才能使用所以起始PC为4只能在4这行字节码指令中使用。 栈帧局部变量表
栈帧中的局部变量表是一个数组数组中每一个位置称之为槽(slot) long和double类型占用两个槽其他类型占用一个槽。i占用数组下标为0的位置j占用数组下标1-2的位置。 刚才看到的是静态方法实例方法中的序号为0的位置存放的是this指的是当前调用方法的对象运行时会在内存中存放实例对象的地址。 方法参数也会保存在局部变量表中其顺序与方法中参数定义的顺序一致。局部变量表保存的内容有实例方法的this对象方法的参数方法体中声明的局部变量。 如下test3方法中包含两个参数k,m这两个参数也会被加入到局部变量表中。 槽数问题
public void test4(int k,int m){{int a 1;int b 2;}{int c 1;}int i 0;long j 1;
}为了节省空间局部变量表中的槽是可以复用的一旦某个局部变量不再生效当前槽就可以再次被使用。
1、方法执行时实例对象this、k、m 会被放入局部变量表中占用3个槽 2、将1的值放入局部变量表下标为3的位置上相当于给a进行赋值。 3、将2放入局部变量表下标为4的位置给b赋值为2。 4、ab已经脱离了生效范围所以下标为3和4的这两个位置可以复用。此时c的值1就可以放入下标为3的位置。 5、最后放入jj是一个long类型占用两个槽。但是可以复用b所在的位置所以占用4和5这两个位置 所以局部变量表数值的长度为6。总而言之JVM会在编译阶段就根据变量如果后续没有使用到的情况就会将它所占用的槽释放让其它变量能占用这个槽从而减少数组占用的内存这一点在编译期间就可以确定了运行过程中只需要在栈帧中创建长度为6的数组即可。 操作数栈 操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。他是一种栈式的数据结构如果一条指令将一个值压入操作数栈则后面的指令可以弹出并使用该值。 在编译期就可以确定操作数栈的最大深度从而在执行时正确的分配内存大小。 比如之前的相加案例中操作数栈最大的深入会出现在这个时刻由于两数相加需要将两个数入到操作数栈进行相应的操作栈的最大深入就是2 帧数据 帧数据主要包含动态链接、方法出口、异常表的引用。 动态链接
当前类的字节码指令引用了其他类的属性或者方法时需要将符号引用编号转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。 方法出口
方法出口指的是方法在正确或者异常结束时当前栈帧会被弹出同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中需要存储此方法出口的地址。 异常表
异常表存放的是代码中异常的处理信息包含了异常捕获的生效范围以及异常发生后跳转到的字节码指令位置。 如下案例i1这行源代码编译成字节码指令之后会包含偏移量2-4这三行指令。其中2-3是对i进行赋值1的操作4的没有异常就跳转到10方法结束。如果出现异常的情况下继续执行到7这行指令7会将异常对象放入操作数栈中这样在catch代码块中就可以使用异常对象了。接下来执行8-9对i进行赋值为2的操作。所以异常表中异常捕获的起始偏移量就是2结束偏移量是4在2-4执行过程中抛出了java.lang.Exception对象或者子类对象就会将其捕获然后跳转到偏移量为7的指令。 栈内存溢出
Java虚拟机栈如果栈帧过多占用内存超过栈内存可以分配的最大大小就会出现内存溢出。Java虚拟机栈内存溢出时会出现StackOverflowError的错误。 如果我们不指定栈的大小JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。 要修改Java虚拟机栈的大小可以使用虚拟机参数 -Xss 。 语法-Xss栈大小 单位字节默认必须是 1024 的倍数、k或者K(KB)、m或者M(MB)、g或者G(GB) 例如 Java -Xss1048576 -Xss1024K -Xss1m -Xss1g 操作步骤如下不同IDEA版本的设置方式会略有不同 1、点击修改配置Modify options 2、点击Add VM options 3、添加参数