- java程序运行时候栈帧结构
- 栈帧:虚拟机栈的元素,是进行方法调用和方法执行的数据结构,栈帧存储了方法的局部变量表,操作数栈,动态连接,方法的返回地址。
- 栈顾名思义,指的就是它的数据结构,这种数据结构的特点就是后进先出,因此只有位于栈顶的栈帧才是有效的,成为当前栈,与这个栈帧相关联的方法成为当前方法。
2.栈帧简介
栈帧主要包括局部变量表,操作数栈,动态链接,方法返回地址等信息,下面一一为大家介绍
- 局部变量表
用于存放方法参数和方法内定义的局部变量,容量以变量槽(slot)为最小单位,一个slot可以存放32位以内的数据结构;因此像double,long这种数据类型应该用两个连续的slot存储。slot第0位索引默认是传递方法所属对象实例引用;为了节省栈帧空间,slot是可以被重用的
- 操作数栈
是先入后出的数据结构,相当于一个用于计算的临时存储局部变量的容器,例如代码 a+b, 则会将a压入操作数栈,b压入操作数栈,再将b拿出来,将a拿出来,计算,再将计算结果压入栈。
- 动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧的所属方法的引用
- 方法返回地址
方法返回之后会做什么呢?首先恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用后指令后面的一条指令等。
3.方法的调用
- 解析
在类加载阶段,会将加载的class文件里面常量池的一部分符号引用解析为直接引用,这种解析能成立的前提就是:方法在程序真正运行之前就有一个可以确定的版本,并且这个版本在运行期是不可用的,符合这类条件的方法有(静态方法,私有方法,实例构造器,父类方法)
- 分派
(1)静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,典型应用就是方法重载,静态分派发生在编译阶段
那么什么时候静态类型呢?举个例子:
public class OverLoad { static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human human){ System.out.println("人类"); } public void sayHello(Man man){ System.out.println("男人"); } public void sayHello(Woman woman){ System.out.println("女人"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); OverLoad overLoad = new OverLoad(); overLoad.sayHello(man); overLoad.sayHello(woman); } } 结果: 人类 人类 这里面 Human woman = new Woman();Human就是静态类型,Woman就是实际类型,重载的时候就是根据静态类型来决定使用哪一个方法版本的。
(2)动态分派
所谓动态分配就是在程序运行期间才决定调用哪个方法版本,并且是按照实际类型调用,java中最好的体现就是重写。动态分派是不会介意方法参数传进来的是个啥,只会关注方法的调用者是个啥。
(3)单分派与多分派
方法的接收者和方法的参数统称为方法的宗量,单分派是根据一个宗量来选择方法版本,多分派是根据多于一个宗量决定方法版本。
java是静态多分派,动态但分派的语言。
4.基于栈的指令集架构与基于寄存器的指令集架构
指令集架构执行架构主流的一般分为两类,(1)基于栈的指令集架构 (2)基于寄存器的指令集架构,jvm采用的是基于栈的指令集架构。
基于栈的指令集架构优点如下:
- 很好的可移植性
- 编译器实现简单
缺点也是有的,那就是跟基于寄存器的指令集架构相比,速度慢一些