什么是栈
栈是一种先进后出(FILO)的数据结构,就像枪的弹夹一样,先压进去的子弹是最后打出来的,一般称之为栈底,而位于“弹夹”最顶端的被称为栈顶。
虚拟机栈解释
顾名思义虚拟机栈是JVM中的栈数据结构,此种数据结构是基于线程的,创建了一个线程就相当于创建了一个虚拟机栈,例如我们最熟悉的main方法启动,就启动了一个虚拟机栈。在线程运行的过程中,数据或者引用被加载到栈中,各种数据或者引用会频繁的入栈出栈,等到线程消亡,虚拟机栈也跟着被释放。
虚拟机栈的大小缺省值为1M,可以用JVM参数-Xss调整大小
参考官方文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
搜索:-Xss
可以发现不同平台的缺省值也不同,总的来说,32位的操作系统对应的是320KB,64位的对应的是1024KB
栈帧
Java中每个方法都会创建一个栈帧(Stack Frame),栈帧就是虚拟机栈的子弹,先创建的先被压入栈,然后调用别的方法的时候又压入一个。
例如代码:
public class StackFrameDemo {
public static void main(String[] args) {
System.out.println(work());
}
public static int work(){
int a = 1;
int b = 1;
int c = a + b;
return c;
}
}
运行main方法时
一个main方法启动创建了一个虚拟机栈,此时虚拟机栈中是这样的:
当我们调用work方法的时候:
等到work()方法执行完,就出栈,然后main方法出栈,出栈的操作可以看做把子弹射出去,虚拟机栈里就没了。
栈帧详解
栈帧的结构
栈帧结构
一个栈帧需要分配多少内存,不会受到程序运行时变量数据影响,仅仅取决于虚拟机的实现
局部变量表(Local Variable Table)
- 在编译代码的时候就可以确定栈帧需要多大的局部表量表,具体的大小可以再编译后的Class文件中看到
- 局部变量表容量以变量槽(Variable Slot)为最小单位,每个变量槽都可以存储32位长度的内存空间
- 在方法执行的时候,虚拟机使用局部变量表完成参数值到参数变量表的传递过程,如果执行的是实例方法,那局部表量表的第0个槽位索引默认是用于传递方法所属对象的引用(this关键字)
- 其余参数则按照参数顺序排列,占用从1开始的局部变量表Slot
- 基本数据类型以及引用和返回地址(returnAddress)占用一个变量槽,long和double占两个
操作数栈(Operand Stack)
- 也是在编译期间就可以确定大小
- 栈帧(Frame)被创建时,操作数栈是空的。操作数栈的每个定都可以存放JVM各种类型的数据,long和double则占两个栈深(和局部变量表是一样的,因为long、double都是64位的数据)
- 方法执行的过程中,随着各个指令的执行,会有各种数据往操作数栈中写入和读取,也就是出栈和入栈操作
- 操作数栈调用其他有返回结果的方法时,会把结果push到栈上(通过操作数栈进行参数传递)
动态链接(Dynamic Linking)
- 动态链接涉及到java的多态特性,动态链接的说明是栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用,该引用的目的是为了支持动态代理
完成出口(也叫返回地址 Return Address)
- 方法执行完成的出口,只能由2种方式可以退出:return 返回指令,异常退出
- 正常退出的流程是,将调用方法的返回值(如果有的话),压入被调用方法的操作数栈顶,被调用的方法根据程序计数器继续执行
通过字节码,查看方法执行时栈帧中的各个数据
-
打开类所在的文件夹,通过javac 指令编译java文件为class字节码文件
在通过javap -c 查看反汇编字节码,如果有中文用 javac -encoding UTF-8 XXX.java
work()方法部分的字节码为
public static int work();
Code:
0: iconst_1 加载一个常量1到操作数栈
1: istore_0 将一个数值从操作数栈加载到局部变量表0的slot
2: iconst_1 加载一个常量1到操作数栈
3: istore_1 将一个数值从操作数栈加载到局部变量表1的slot
4: iload_0 将局部变量表0位置的数值加载到操作数栈
5: iload_1 将局部变量表1位置的数值加载到操作数栈
6: iadd 加法指令
7: istore_2 将一个数值从操作数栈加载到局部变量表2的slot
8: iload_2 将局表变量表位置为2的数值加载到操作数栈
9: ireturn 返回,将操作数栈顶的返回值压入main方法的操作数栈顶
关于字节码指令的解释参考博客:https://cloud.tencent.com/developer/article/1333540
用图形时间轴看起来更加明了
这就是work方法执行时,内存中数据的变化。
本地方法栈
与虚拟机栈类似,只不过,虚拟机栈用于管理Java方法的调用。而本地方法栈则用于管理native方法(例如Thread.start0())的调用,由于native方法不是用Java实现的,而是由C语言实现的。事实上虚拟机栈和本地方法栈的区域是非常类似的,在Hotspot中直接把本地方法栈和虚拟机栈合二为一,放在了一个区域之中。当然他们本质上的是两个东西。