Java虚拟机栈
JVM虚拟机栈是线程私有的,每个线程都具有一个虚拟机栈,其内部保存一个个栈帧,对应着每个方法的调用。生命周期和线程生命周期相同。
虚拟机栈作用
主管java程序的运行,进入的栈帧存储着局部变量表,操作数栈,动态连接,方法返回值等。
栈帧:
栈帧是虚拟机栈的基本单位,栈帧的调入对应着方法的调用,栈帧的弹出对应着方法的结束返回,其中,由于只有弹栈和入栈的操作,java虚拟机栈没有GC机制,但在栈空间不够时会出现StackOflowError错误,java虚拟机允许栈的大小是动态或者固定不变的(可通过-Xss命令实现栈的扩展),尽管可以通过命令调整栈大小,但不能无限制扩展,当栈无法申请到足够的内存会抛出OutOfMemoryError错误。
运行时栈帧结构
栈帧包括局部变量表,操作数栈,动态连接,方法返回地址和一些附加信息。其中局部变量表和操作数栈最为重要。
局部变量表
局部变量表定义为一个数字数组,用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括基本数据类型,引用数据类型,及returnnAddress类型。
由于局部变量表建立在线程的栈上,因此不存在数据安全问题。
局部变量表所需容量大小在编译器确定下来,并保存在方法的Code属性的max local variables数据项中,方法运行期间不会改变局部变量表大小。
局部变量表中的变量只在当前方法中有效,方法调用结束后,随着方法栈帧的弹出,局部变量表随之销毁。
槽(slot)
- 变量槽是局部变量表中的最小单位。槽没有明确的内存空间大小, char,byte,int,short,float,引用类型和returnAddress占1个槽,而double和long类型占2个槽。
- JVM会为每个槽都分配一个访问索引,通过这个索引便可以访问到局部变量表中指定的局部变量。
- 当一个方法被调用时,方法的参数和方法体内的局部变量将会按顺序复制到局部变量表的slot上
- 如果访问占2个槽的局部变量,只需使用第一个槽的索引
- 如果当前栈是由构造方法或者实例方法创建的,那么该对象引用this将放在index为0的slot处,其余参数继续列。
- 局部变量表中的变量是垃圾回收根节点,只要被局部变量表中直接或间接引用的对象不会被回收。
操作数栈
在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据。操作数栈是JVM执行引擎的一个工作区,当一个方法开始执行时,一个新的栈帧也会被创建。
每个操作数栈都有个明确的深度用于存储,深度在编译期间就确定好了,保存在方法的Code属性中,为max_stack的值。
操作数栈中,32bit的占1一个栈,64bit的占2个栈。
public static void add(java.lang.String, int, int, java.util.List<java.lang.String>);
Code:
0: iload_1
1: ifne 20
4: iload_2
5: ifne 20
8: aload_3
9: aload_0
10: invokevirtual #13 // Method java/lang/String.toString:()Ljava/lang/String;
13: invokeinterface #14, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。引用的目的是为了实现动态链接。
在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用,保存在class文件的常量池里。
方法返回地址
方法执行后,有2种方法退出:
1.正常执行并返回
2.方法产生异常,异常并未处理
无论哪种方法退出,在方法退出后都返回该方法的被调用的位置,方法正常退出时,调用方法的PC计数器的值作为返回地址,通过异常退出的,返回地址要通过异常表来确定,栈帧中一般不会保存这部分信息,并不会给上层调用者产生任何返回值。
以上时鉴于阅读《深入理解JAVA虚拟机》的第8章时的理解,本文章只为了记录自己的学习心得,有不对的地方欢迎留言指出。