JVM之虚拟机栈
每个线程有一个私有栈,随着线程的创建而创建。栈里面有栈桢,每个方法创建一个栈桢,方法的调用是入栈的过程,方法调用完返回就是出栈的过程。栈的大小可以动态扩展,当栈的调用深度大于JVM所允许的范围,会抛出StackOverflowError。
java栈的组成元素-------栈桢
栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,他们是按字长计算的。但调用一个方法时,它从类型信息中得到此方法局部变量区和操作数栈大小,并据此分配栈内存,然后压入Java栈。
栈桢的特点
- 每个方法从调用开始到执行完成的过程,对应一个栈桢在虚拟机栈中,从入栈到出栈的过程。
- 活动线程中,只有栈顶的栈桢是有效的,称为当前栈桢,这个栈桢关联的方法叫当前方法。
局部变量表
主要作用:存放方法参数和方法内的局部变量
局部变量表被组织为以一个字长为单位、从0开始计数的数组,类型为short、byte和char的值在存入数组前要被转换成int值,而long和double在数组中占据连续的两项,在访问局部变量中的long或double时,只需取出连续两项的第一项的索引值即可,如某个long值在局部变量区中占据的索引时3、4项,取值时,指令只需取索引为3的long值即可。
操作数栈
主要作用:数据计算时,临时数据的存储存储区域,通过入栈和出栈来存储数据
和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。可把操作数栈理解为存储计算时,临时数据的存储区域。
桢数据区
主要作用:访问常量池中的数据、正常方法返回、异常派发机制
访问常量池数据
当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。
正常方法返回
如果是通过return正常结束,则当前栈帧从Java栈中弹出,恢复发起调用的方法的栈。如果方法有返回值,JVM会把返回值压入到发起调用方法的操作数栈。
异常机制
当异常抛出时,JVM给catch块中的代码。如果没发现,方法立即终止,然后JVM用帧区数据的信息回复发起调用的方法的帧。然后再发起调用方法的上下文重新抛出同样的异常。
栈的运行过程
代码
class Example3C{
public static void addAndPrint(){
// addAndPrint中调用了addTwoTypes
double result = addTwoTypes(1,88.88);
System.out.println(result);
}
public static double addTwoTypes(int i, double d){
return i+d;
}
}
执行过程的三个快照
上图说明了两件事
- 只有在调用一个方法时,才为当前栈分配一个帧,然后将该帧压入栈
- 帧中存储了对应方法的局部数据,方法执行完,对应的帧则从栈中弹出,并把返回结果存储在调用 方法的帧的操作数栈中
JVM配置
-Xss128K,每个线程栈的大小为128K,如果不设置,JDK5.0以后用默认值1M,之前默认值是256K。
在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一 个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
栈空间调忧要注意
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
JVM可创建的最大线程数限制因素
线程堆栈大小——》进程的最大内存——》操作系统位数
面试题
1. 栈桢由那三部分组成,各自存储了什么数据?
2. 操作指令在栈桢中是如何工作的?
3. 如果发生零除异常,它是如何被抛出的?