栈帧是虚拟机运行时数据区的java虚拟机栈的基本单位。栈帧是支持虚拟机进行方法调用和方法执行的数据结构,栈帧中存储了方法的局部变量表和操作数占、动态链接、方法返回地址等信息。每一个java方法从开始调用到执行结束,都对应着一个栈帧在虚拟机栈里面的入栈到出栈的过程。
栈帧中需要多大的局部变量表、多深的操作数栈,在编译期间都已经完全确定下来了。与运行期间的变量数据无关。
在活动的当前线程中,只有位于栈顶的栈帧才是有效地,被称为当前栈帧,这个栈帧相关联的方法是当前方法,执行引擎运行的字节码指令都只针对当前栈帧进行操作。
是一组变量存储空间,用于存放方法参数和方法内部定义的局部变量。在java程序编译class文件的时候,就已经确定了局部变量表的大小。
slot(变量槽)
虚拟机中没有直接说明一个slot确切的空间大下。但规定了Slot中可以存放boolean、byte、char、short、int、float、reference、returnAddress这八种数据类型。
实例方法(非static方法)的局部变量表的空间分配
在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递。局部变量表的第0个索引的Slot默认是传递方法所属对象实例的引用。即this方法参数表开始一次排列方法体内部定义的变量,按顺序分配。两种退出方法的方式
return语句异常退出方法时可能执行的操作:
恢复上层方法的局部变量表和操作数栈;把返回值压入调用者栈帧的操作数栈;调整 PC 计数器指向方法调用后面的指令。方法调用并不等同于方法执行,方法调用的唯一目的就是确定要调用哪个方法,但并不涉及方法内部的执行。一切方法调用在class文件存储的都只是符号引用,而不是方法实际执行的直接引用。符号引用转换成直接引用,需要到类加载阶段甚至是运行阶段才能确定下来。
方法调用主要分为两种,解析调用和分派调用。
invokestatic:调用静态方法;
invokespecial:调用构造器方法、私有方法、父类方法;
invokevirtual:调用所有虚方法,除了静态方法、构造器方法、私有方法、父类方法、final 方法的其他方法叫虚方法;
invokeinterface:调用接口方法,会在运行时确定一个该接口的实现对象;
invokedynamic:在运行时动态解析出调用点限定符引用的方法,再执行该方法。
其中,只要是被invokestatic、invokespecial两种指令调用的方法,都可以在解析阶段唯一确定调用版本,也就是调用那个方法。主要包括静态方法、私有方法、实例构造器、父类方法,这些方法在类加载的时候,会直接把符号引用解析为方法的直接引用
这些方法我们称之为非虚方法,除了final修饰的方法外,其余方法被称为虚方法,final方法也是一种非虚方法,但是并不和上四类方法一起讨论。
分派又分为动态分派和静态分派,静态分派主要体现在重载,动态分派可以在方法重写上体现。
静态分派:所有依赖静态类型来定位方法执行版本的分派动作成为静态分派
主要体现:方法重载
期间:编译期间
代码体现:
package com.wangye.Test; public class Main { static abstract class Human{} static class Man extends Human{} static class Woman extends Human{} public void sayHello(Human human){ System.out.println("Human"); } public void sayHello(Man man){ System.out.println("Man"); } public void sayHello(Woman woman){ System.out.println("Woman"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); Main main = new Main(); main.sayHello(man); main.sayHello(woman); } }输出
Human Human 上述代码中,我们将Human作为静态类型或者可以成为外观类型,Man、Woman都是实际类型类的方法重载机制,在选择哪个方法时,主要考虑传入的参数和数据类型,而虚拟机在重载的时候,是根据参数的静态类型做出判断依据,不是实际类型。动态分派
根据静态分派的定义和性质,我们可以知道,动态分派肯定是根据实际类型来做出判断的
动态分派主要体现在方法的重写上。
期间:运行期间
代码体现:
package com.wangye.Test; public class Override { static abstract class Human{ protected abstract void sayHello(); } static class Man extends Human{ @java.lang.Override protected void sayHello() { System.out.println("man hello"); } } static class Woman extends Human{ @java.lang.Override protected void sayHello() { System.out.println("Woman hello"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man =woman; man.sayHello(); } }输出:
man hello Woman hello Woman hello可以看到,sayHello方法的运行,实际上是根据实际类型决定的。
解析一定是静态的过程,并且在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转为直接引用,不会延迟到运行期间。
分派分为静态分派和动态分派,静态分派是在编译期间完成的,但是动态分派是在运行期间完成的。
- 可以看到,sayHello方法的运行,实际上是根据实际类型决定的。解析一定是静态的过程,并且在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转为直接引用,不会延迟到运行期间。
分派分为静态分派和动态分派,静态分派是在编译期间完成的,但是动态分派是在运行期间完成的。