JVM的执行引擎—栈是JVM中最核心的组成部分,负责字节码的执行和计算,在代码中定义的所有的运行逻辑都是在方法中定义,方法在JVM中会压栈运行,在运行完成之后会进行出栈,在执行的过程中,会由堆、方法区、程序计数器等进行配合才可以完成一次完整的执行
运行时栈帧结构
当方法入栈执行的时,在栈中会新城一个栈帧,栈帧是栈中运行的基本单位,每个栈帧都包含了局部变量表、操作数栈、动态链接、方法出口等。当方法执行完成,栈帧会销毁释放内存。在当前执行线程的栈顶的栈帧称为当前栈即为有效栈,即正在执行的栈帧。
局部变量表
局部变量表示变量存储的空间,该内存中保存的是方法参数和方法内定义的局部变量
如果方法中定义的引用类型,也会保存到局部变量表中吗?
定义的基本类型的局部变量,在栈帧出栈时,也一起被回收,但是引用类型一般是保存在堆中的,那么在方法中创建的引用类型是保存在局部变量表还是堆呢?这个需要看当前定义的对象是否发生逃逸,如果没有发生逃逸,那么将该对象分配在栈上,如果发生逃逸,就将该对象分配到堆中。
操作数栈
保存在代码执行过程中产生的变量或者操作数,一般是计算或者多步操作的中间变量。例如:在做算术运算的时候是通过操作数栈进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
动态链接
在进行方法调用的时候,是通过动态链接来获取被调用方法的直接应用,进而通过操作数栈传递参数,进行方法的调用。(方法调用在Class文件中存储的就是符号引用,并不是方法实际运行时的内存入口地址,需要通过动态链接进行解析)
在类加载的过程中,解析阶段主要是将符号引用转换为直接引用,称为静态解析,并且只转换了一部分。另外一部分是通过在运行期间将符号引用转换为直接引用,这部分就被称为动态链接
方法出口
一个方法开始执行之后,有两种方式可以退出这个方法,一种是方法正常执行,直到遇到方法返回指令,被称为正常方法出口。另一种是在执行的过程中发生异常,那么也会退出方法,被称为异常方法出口。方法出口即结束当前方法执行,将当前栈帧出栈,并将返回值(如果有的话)压入调用者栈帧的操作数栈中。
重载和重写
**重载:**在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同
**重写:**子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
Human man = new Man()
**静态类型:**代码中Human
称为变量的静态类型
**动态类型:**代码中Man
称为变量的实际类型
重载的本质
- 示例代码:
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello, guy!");
}
public void sayHello(Man man) {
System.out.println("hello, man!");
}
public void sayHello(Woman woman) {
System.out.println("hello, woman!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch dispatch = new StaticDispatch();
dispatch.sayHello(man);
dispatch.sayHello(woman);
}
}
- 输出
hello, guy!
hello, guy!
-
本质
虚拟机在重载时通过参数的静态类型而不是实例类型作为判定依据。静态类型在编译阶段是可知的,所以在编译阶段就已经根据静态类型决定使用哪个版本。依赖静态类型来定位方法执行版本的分派称为静态分派,静态分派的典型应用就是重载
所以重载通过静态类型
Human
确定方法执行的版本,使用的是参数为Human
的重载方法
重写的本质
- 示例代码
public class StaticDispatch {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("hello, man!");
}
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("hello, woman!");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
}
}
- 输出
hello, man!
hello, woman!
-
重写的本质
重写的本质是通过实际类型找到指定的对象,并调用对应的方法。将这种根据实际类型找到方法的符号引用并映射到对应的直接引用称为动态分派。动态分派的典型应用为重写