JVM中的执行引擎在执行java代码的时候,通常有解释执行(经过解释器执行)和编译执行(经过即时编译器产生本地代码执行)两种选择。java
栈帧程序员
栈帧是用于支持快看小说网虚拟机进行方法调用和方法执行的数据结构,它位于虚拟机栈里面。数据结构
每一个他的偏执欲方法从调用开始到执行完成的过程当中,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。ide
特色:
(1)栈帧包括了局部变量表,操做数栈等,究竟是须要多大的局部变量表,多深的操做数栈是在编译期肯定的。由于一个栈帧须要分配多少内存,不会受到程序运行期变量数据的影响。优化
(2)两个栈帧之间的数据共享。在概念模型中,两个栈帧是彻底独立的,可是在虚拟机的实现里会作一些优化处理,令两个栈帧出现一部分重叠。这样在进行方法调用时,就能够共用一部分数据,无须进行额外的参数复制传递。spa
局部变量表是一组江亚顾飞远变量值存储空间,用于存放方法参数和方法内部定义的局部变量。3d
//方法参数 max(int a,int b)
int a;//全局变量 void say(){ int b=0;//局部变量 }
局部变量和类变量(用static修饰的变量)不一样
类变量有两次赋初始值的过程:准备阶段(赋予系统初始值)和初始化阶段(赋予程序员定义的初始值)。因此即便在初始化阶段没有为类变量赋值也不要紧,它仍然有一个肯定的初始值。
但局部变量不同,若是定义了,但没有赋初始值,是不能使用的。对象
当一个方法刚刚神级保镖开始执行的时候,这个方法的操做数栈是空的,在方法的执行过程当中,会有各类字节码指令往操做数栈中写入和提取内容,也就是出栈、入栈操做。blog
例如,计算:图片
int a=2+3
操做数栈中最接近栈顶的两个元素是2和3,当执行iadd指令时,会将2和3出栈并相加,而后将相加的结果5入栈。
Class文件的常量池中存有大量的符号引用愿岁月可回首,字节码中的方法调用指令就以常量池中指向方法的符号引用做为参数。这些符号引用分为两部分:
静态解析:在类加载阶段或第一次使用的时候就转化为直接引用。
动态连接:在每一次运行期间转化为直接引用。
当一个方法开始执行后,一往情深,傅少爱妻如命只有两种方式能够退出这个方法:正常退出、异常退出。不管采用何种退出方式,在方法退出以后,都须要返回到方法被调用的位置,程序才能继续执行。
当方法正常退出时
调用者的PC计数器做为返回地址。栈帧中通常会保存这个计数器值。
当方法异常退出时
返回地址是顾小秋霍言城要经过异常处理器表来肯定的。栈帧中通常不会保存这部分信息。
方法调用
方法调用是肯定调用哪个方法。
对“编译器可知,运行期不可变”的方法进行调用称为解析。符合这种要求的方法主要包括
分派讲解了虚拟机如何肯定正确的目标方法。分派分为静态分派和动态分派。讲解静动态分派以前,咱们重生80:肥妻喜临门先看个多态的例子。
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 say(Human hum){ System.out.println("I am human"); } public void say(Man hum){ System.out.println("I am man"); } public void say(Woman hum){ System.out.println("I am woman"); } public static void main(String[] args){ Human man = new Man(); Human woman = new Woman(); StaticDispatch sr = new StaticDispatch(); sr.say(man); sr.say(woman); } }
运行结果是:
I am human I am human
总裁霸爱萌妻乖乖沦陷为何会产生这个结果呢?
由于编译器在重载时,是经过参数的静态类型而不是实际类型做为判断依据的。在编译阶段,javac编译器会根据参数的静态类型决定使用哪一个重载版本,因此两个对say()方法的调用实际为sr.say(Human)。
在运行期根据实际类型肯定方法执行版本的分派过程。它的典型应用是重写。
public class DynamicDispatch{ static abstract class Human{ protected abstract void say(); } static class Man extends Human{ @Override protected abstract void say(){ System.out.println("I am man"); } } static class Woman extends Human{ @Override protected abstract void say(){ System.out.println("I am woman "); } } public static void main(String[] args){ Human man = new Man(); Human woman = new Woman(); man.say(); woman.say(); man=new Woman(); man.say(); } }
I am man I am woman I am woman
这彷佛才是咱们平时敲的java代码。对于方法重写,头号追妻令:老婆,休想逃在运行时才肯定调用哪一个方法。因为Human的实际类型是man,所以调用的是man的name方法。其他的同理。
动态分派的实现依赖于方法区中的虚方法表,它里面存放着各个方法的实际入口地址。若是某个方法在子类中被重写了,那子类方法表中的地址将会替换为指向子类实现版本的入口地址,不然,指向父类的实现入口。
单分派和多分派:
方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,分为单分派和多分派。
在静态分派中,须要调用者的实际类型和方法参数的类型才能肯定方法版本,因此其是多分派类型。在动态分派中,已经知道了参数的实际类型,因此此时只需知道方法调用者的实际类型就能够肯定出方法版本,因此其是单分派类型。综上,java是一门静态多分派,动态单分派的语言。
字节码解释执行引擎
虚拟机中的字节码解释执行引擎是基于栈的。下面经过一段代码来仔细看一下其解释的执行过程
public int calc(){ int a = 100; int b = 200; int c = 300; return (a + b) * c; }
第一步:将100入栈。
第二步:将操做栈中的100出栈并存放到局部变量中。后面的200,300同理。
第三步:将局部变量表中的100复制到操做数栈顶。叶星辰厉景墨
第四步:将局部变量表中的200复制到操做数栈顶。
第五步:将100和200出栈,作整型加法,最后将结果300从新入栈。
第六步:将第三个数300从局部变量表复制到栈顶。接下来就是将两个300出栈,进行整型乘法,将苏允最后的结果90000入栈。
第七步:方法结束,将操做数栈顶的整型值返回给此方法的调用者。