虚拟机方法调用和字节码执行

执行引擎是Java虚拟机最核心的组成部分之一。虚拟机和物理机都有代码执行能力,其别是物理机的执行引擎建立在处理器,硬件,指令集和操作系统上。而虚拟机的执行引擎是自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。

栈帧是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面入栈到出栈的过程。

在编译期间,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并写入到方法表的code属性之中,所以一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,仅仅取决于具体的虚拟机实现。

在一个线程中有多个栈帧,只有位于栈顶的栈帧才是有效的。

局部变量表:
存放方法参数和方法内定义的局部变量,在编译class文件时就在方法的code属性的max_locals中确定了该方法所需要分配的局部变量表的最大容量。
局部变量表以槽(slot)为单位,每个slot中可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean,byte,char,int,float,reference,returnAddress 8种类型,还有long和double是64位的数据结构,占用2个slot。每一个reference类型表示对一个对象实例的引用,每个reference应该做到两点:一是从这个引用中直接或者间接地查找到对象在Java堆中的数据存放的起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息。
虚拟机是使用局部变量表来完成参数值到参数变量列表的传递过程。如果是实例方法,局部变量表的第0位索引默认用于传递所属对象实例的引用,方法可以通过this来访问这个隐含的参数。
内存空间是否能被回收就是看局部变量表是否还存在关于这对象的引用。
局部变量定义了但是没有赋初始值是不能被使用的。

操作数栈:
操作数栈是后入先出的栈,也是在编译的时候写到code属性的max_stacks数据栈中。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。

动态链接:
每个栈帧都包含一个指向运行时常量池中该栈帧所述方法的引用,持有这个引用就是为了支持方法调用过程中的动态连接。
class文件常量池中有大量的符号引用,这些符号引用一部分在类加载阶段或者第一次使用时(初始化)就转换成直接引用,这种转化成为静态解析。另一部分在每一次运行期间转化成直接引用,这部分称为动态连接。

方法返回地址:
方法有两种退出方式:一种正常退出称为正常完成出口,另一种异常退出成为异常完成出口。
无论是那种退出方法,推出后都返回方法被调用的位置,程序才能正常执行。方法推出的过程实际上就等同于把当前的栈帧出栈。

方法调用
方法调用并不等同于方法执行。方法调用唯一的任务就是确定被调用方法的版本。
解析调用
调用目标在程序代码写好,编译器进行编译时就必须确定下来。这类方法的调用叫解析调用。“编译期可知,运行期不可变”,所以静态方法和私有方法适合在类加载阶段进行解析。只要是被invokestatic和invokespecial指令调用的方法,都可以再解析阶段确定调用版本,满足这个条件的还有实例构造器,父类方法,final方法(不能被覆盖,没有其它版本)都类加载阶段进行解析。
解析调用在类的解析阶段就把涉及的符号引用全部转变成可确定的直接引用,不会延迟到运行期去完成。
分派调用
静态分派
Human man = new Man();
这里的”Human”是变量的静态类型,”Man”是变量的实际类型。静态类型在编译期可知,实际类型在运行时可知。虚拟机在重载时通过参数的静态类型作为依据,而静态类型编译期可知,所以在编译阶段javac编译器会根据参数的静态类型决定使用哪个重载版本。
动态分派
重写方法的时候
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
他们在class文件中都是invokevirtual Human.sayHello:()V,这个时候关键就在于invokevirtual 指令。invokevirtual指令执行的第一步就是在运行期确定接受者的实际类型。所以两次调用invokevirtual指令会把常量池中的类方法引用符号解析到不同的直接引用上,这种分派叫做动态分派。
Father father =new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new _qq());
在上面编译阶段选择,静态分派,两条指令分别指向Father.hardChoice(360),Father.hardChoice(qq)。所以静态分派属于多分派类型。
在运行阶段虚拟机选择,动态分派,在执行son.hardChoice(new _qq());这句话时,就是执行invokevirtual指令,编译期已经决定了目标方法的前面是hardChoice(qq),唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型是Father还是son,所以动态分派是但分派类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值