执行引擎执行java代码执行可能会 解释执行(通过解释器) 和编译执行(通过即时编译器产生本地代码) 也可能
两者都用
栈帧 :用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素,存储了:方法的局部变量表、操作数栈、动态连接和方法返回地址
一个栈帧分配的大小取决于jvm的实现
局部变量表:是一组变量值存储空间,用于存放方法参数以及方法内部定义的局部变量,在编译阶段在Code属性中确定了容量
以变量槽(Variable Slot)为最小单位,可以存放boolean,byte,char,short,int,float,reference
,returneAddress这八种数据类型即可,一般而言32位 long,double占两个slot!!!
局部变量表的0号索引存放的是该方法所在类的引用.即this 之后的是方法的参数列表自己局部变量
Slot可重用,一般而言,当当前字节码PC计数器的值已经超过了某个变量的作用域,那么就可以给其他的变量使用
slot复用导致了一个问题,就是有的局部变量并不是整个方法可见的,当超过方法内部,某个局部变量的作用域的时候,没有声明新的局部变量,那么原来的过期的局部变量的slot的值不会被擦除,那么就会导致该过期的局部变量的引用的内存空间不会被回收。
操作数栈,编译的时候Code属性确定深度,每一个元素可以是任意的Java数据类型,64位数据类型占两个栈容量。
通常优化为两个栈帧会有重复部分,即栈帧一的操作数栈的一部分与栈帧二的局部变量表的一部分共享数据!
栈帧信息:动态连接,方法返回地址,其他附加信息。
方法调用:确定被调用方法的版本(即确认调用哪一个方法),不一定要确认方法的直接引用
解析: 编译期可知,运行期不变的 方法主要有 静态方法和私有方法,这两种会直接解析出直接引用
invokestatic,invokespecial,invokevirtual,invokeinterface,invokedynamic
只要被invokestatic,invokespecial指令调用的方法都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法
,私有方法,实例构造器,父类方法。类加载的时候就会把符号引用解析为直接引用,这些方法称为非虚方法(被final
修饰也是)。
分派:
1.静态分派:静态类型(外观类型),实际类型 ,应用于重载选择方法
jvm重载通过参数的静态类型作为判定。变长参数优先级最低
char->int->long->float->double->Character->接口->Object->变长数组
2.动态分派 根据实际类型在选择执行方法 多态
3.单分派与多分派
java是一门静态多分派,动态单分派的语言
编译器的选择过程为静态分派过程,依据有两点:1.静态类型是什么,2.方法参数是什么
运行阶段虚拟机选择为动态分派,在编译阶段已经确定了方法签名,传过来的参数为QQ,不
管QQ是什么样的,是“奇瑞”还是“腾讯”
根据一个宗量的类型进行方法的选择称为单分派
根据多于一个宗量的类型对方法的选择称为多分派
再看定义:方法的接受者与方法的参数统称为方法的宗量。
下面看看编译阶段编译器的选择过程,也就是静态分派的过程。
这个时候,选择目标方法依据两点:
一个是静态类型Father和Son,
二是方法参数Ipad和Iphone。
这次选择的结果的最终产物是产生了两条invokevirtual指令,两条指令的参数分别为常量池中指向Father.hardChoice(ipad)及Father.hardChoice(iphone)方法的符号引用。因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。
再看看运行阶段虚拟机的选择,也就是动态分派的过程。
在执行son.hardChoice(new Ipad())这句代码时,由于编译期已经决定目标方法的签名必须为hardChoice(Ipad),虚拟机不会关心传递过来的参数到底是什么,因为这时参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型到底是Father还是Son。因为只有一个宗量作为选择一句,所以Java语言的动态分派属于单分派类型。
4.jvm动态分派的实现
由于元数据量大,处于性能的考虑,会有一个虚方法表来记录改类的方法用于查找
个人理解:
静态分派:这里关注点是方法参数列表,列表中参数的 外观类型 决定了调用哪一个方法。
public class human{
}
public class man extends humen{
}
public class Test{
public static void who(human h){
System.out.println("human ");
}
public static void who(man m){
System.out.println("man ");
}
public static void main(){
humen h = new human(); // 外观human 实际 human
humen h1 = new man(); // 外观human 实际 man
man m = new man(); // 外观man 实际 man
Test.who(h); //System.out.println("human ");
Test.who(h1); //System.out.println("human "); 这里关注的是 参数 外观类型
//而不是实际类型
Test.who(m); //System.out.println("man ");
}
}
动态分派:根据 实际类型 来决定调用哪一个方法
public class human{
void say(){
System.out.println("human ");
}
}
public class man extends humen{
void say(){
System.out.println("man ");
}
}
public class Test{
public static void main(){
human h = new human(); // 外观human 实际human
human h1 = new man(); // 外观human 实际 man
man m = new man(); // 外观man 实际 man
h.say(); // System.out.println("human ");
h1.say(); // System.out.println("man "); 这里需要关注 调用者 的实际类型
m.say(); //System.out.println("man ");
}
}
动态分派已经决定定了 方法的 调用者 参数列表 然后参数列表 中的值 为一个抽象的东西,如车,只要
是车,就不会管是什么样子的车 ,所以是 单分派
静态分派决定了 方法的调用者,参数列表是没有决定的 参数列表中可以是车也可以是飞机,动物等,所以是
多分派
即 : 调用者的 外观类型与实际类型是否一致, 决定了 是静态分派还是动态分派
参数列表的 外观类型是否唯一 决定了 单分派和多分派