栈
栈是Java中最常用的容器之一,只有一个口(入口和出口共用一个),里面的元素进出规则是先进后出。
Java中每个方法的执行都意味着入栈,执行完成后出栈,所以说是最常用的容器之一也不为过。
如上图中代码所示:
- 方法调用顺序是:main() —> method01() —> method02() —> method03() —> method04() 。
- 方法入栈顺序是:main() —> method01() —> method02() —> method03() —> method04() 。
- 方法执行顺序是:method04() —> method03() —> method02() —> method01() —> main() 。
- 方法出栈顺序是:method04() —> method03() —> method02() —> method01() —> main() 。
方法入栈顺序与方法调用顺序相同,方法出栈顺序与方法执行顺序相同。
上诉所有方法均在同一个线程中,根据先进后出原则,完整流程应该是:
- 调用main(),main()入栈。
- 调用method01(),method01()入栈,压在main()之上。
- 调用method02(),method02()入栈,压在method01()之上。
- 调用method03(),method03()入栈,压在method02()之上。
- 调用method04(),method04()入栈,压在method03()之上。
- method04()执行完,method04()出栈。
- method03()执行完,method03()出栈。
- method02()执行完,method02()出栈。
- method01()执行完,method01()出栈。
- main()执行完,main()出栈,执行完毕。
正在执行的方法都必须位于栈顶,若是想要执行其他方法,只要栈上还有其他方法压着,压着的方法必须依次出栈,将该方法置于栈顶,该方法才能被执行。
栈帧
栈帧是栈的组成单元,栈里面的元素都是以栈帧的形式存储。栈帧由局部变量表,操作数栈,动态连接,方法返回地址,一些附加信息组成。
- 局部变量表
局部变量表用于存储方法参数和方法内的局部变量。实例方法中,0索引位置默认为this,表示对方法所有对象实例的引用。存储变量的位置为Slot,也可称之为卡槽,卡槽是可以复用的,方法内的局部变量有自己的作用域,超出变量的作用域,该卡槽就可以被复用,复用之后可能会引用内存回收的一些问题。 - 操作数栈
操作数栈可以储存各种类型的数据,栈容量叫"字宽",32位数据类型栈容量为1,64位数据类型栈容量为2;32位虚拟机一个字宽占4个字节,32位;64位虚拟机一个字宽占用8个字节,64位。操作数栈主要用来对局部变量表中的元素进行写入和提取,也有传递参数的作用,但不进行运算处理。
当多个方法之间有共享的数据,则栈帧之间会有重叠,操作数栈的共享区域和另外方法的局部变量表共享区域重叠,从而无须进行额外的参数传递。 - 动态连接
动态连接指向运行时常量池中该栈帧所属方法的引用,记录方法执行状态,保证方法正确执行。 - 方法返回地址
记录该方法调用者执行指令,不管是正常返回还是异常返回,都需要返回到该方法被调用的位置。如:调用者在代码偏移量为4的位置调用了该方法,那么该方法的方法返回地址就为4,调用者会从代码偏移量为5的位置继续执行程序。方法返回地址的使用通常意味着该方法执行完毕,已被出栈。 - 附加信息
若存在一些虚拟机规范中无法描述的信息,则会存储在附加信息位置。开发中一般会把动态连接,方法放回地址,附加信息统称为栈帧信息。