一、运行时栈帧结构
栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构,也是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态链接、方法返回地址。
在活动线程中,只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是有效的。
1)局部变量表
一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
变量槽(Slot)为最小单位
boolean、byte、char、short、int、float、reference、returnAddress分别占用一个变量槽。
long、double分别占用两个变量槽。
当一个方法被调用时,Java虚拟机会使用局部变量表来完成参数值到参数变量列表的传递过程,即实参到形参的传递。
当执行的是非static修饰的方法时,布局变量表中第0位索引的变量槽默认为传递方法所属对象实例的应用。
为了节省栈帧耗用的内存空间,局部变量表中的变量槽是可以重用的。
2)操作数栈
一个后进先出的栈。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令向操作数栈中写入和读取内容,即出栈入栈操作。
优化处理:令两个栈帧出现一部分重叠;节约空间、在记性方法调用时可以直接公用一部分数据,无须进行额外的参数赋值传递了。
3)动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接
4)方法返回地址
当一个方法开始执行后,只有两种方式能退出该方法。
正常调用完成:执行引擎遇到任意一个方法返回的字节码指令。
异常调用完成:在方法执行的过程中遇到了异常,并且没有catch或throw的异常。
二、方法调用
1)解析
调用不同类型方法的指令
invokestatic:调用静态方法。
invokespecial:调用实例构造器()方法、私有方法和父类的方法。
invokevirtual:调用所有的虚方法。
invokeinterface:调用接口方法。
invokedynamic:运行时动态解析出调用点限定符引用的方法,在执行该方法。
方法调用方式:静态(在编译期间就完全确定,在类加载的解析阶段就会把符号引用转变为直接引用);分派
2)分派
静态分派
所有依赖静态类型来决定方法执行版本的分派动作。
典型应用表现:方法重载
动态分派
典型应用表现:方法重写
单分派与多分派
虚拟机动态分派的实现
invokevirtual运行时解析过程
找到操作栈顶的第一个元素所指向的对象实际类型C。
如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限检验,如果通过则返回这个方法的直接引用,查找过程结束。不通过则返回java.lang.IllegalAccessError异常。
否则,按照继承关系从下往上依次堆C的各个父类进行第二步的搜索和验证过程。
如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
三、基于栈的字节码解释执行引擎
1)基于栈的指令集与基于寄存器的指令集
基于栈的指令集:Javac编译器输出的字节码指令流,字节码指令流里面的指令大部分都是零地址指令,它们依赖操作数栈进行工作。
举例说明:
iconst_1
iconst_1
iadd
istore_0
基于寄存器的指令集:主流PC机中物理硬件直接支持的指令集架构,依赖寄存器进行工作。
举例说明:
mov eax, 1
add eax, 1
特点:
基于栈的指令集可移植,基于寄存器的指令集由硬件控制,受到硬件的约束;
基于栈的指令集代码相对更加紧凑(字节码中每个字节就对应一条指令,而多地址指令集中还需要存放参数),但是完成相同功能所需的指令数量也会多。
编译器实现更加简单(不需要考虑空间分配的问题,所需空间都在栈上操作)
基于栈的指令集执行速度相对慢
基于栈的指令集实现在内存中,频繁的内存访问,是执行速度的瓶颈
2)基于栈的解释器执行过程
执行偏移地址为0的指令,bipush指令的作用是将单字节的整形常量值(-128~127)推入操作数栈顶,跟随一个参数,指明推送的常量值100。
执行偏移地址为2的指令,istore_1指令的作用是将操作数栈顶的整型值出栈并存放在第1个局部变量槽中。同理,偏移量地址为3,4,7,10的指令操作。
执行偏移地址为11的指令,iload_1指令的作用是将局部变量表第1个变量槽中的整形值复制到操作数栈顶。
执行偏移地址为12的指令,iload_2指令的执行过程与iload_1类似,把第2个变量槽的整型值入栈。
执行偏移地址为13的指令,iadd指令的作用是将操作数栈中头两个栈顶元素出栈,做整形假发,然后把结果重新入栈。在iadd指令执行完毕后,栈中原有的100和200被出栈,它们的和300被重新入栈。
执行偏移地址为14的指令,iload_3指令把存放在第3个局部变量槽中的300入栈到操作数栈中。这时操作数栈为两个整数300,。下一条指令imul是将操作数栈中头两个栈顶元素出栈,做整形乘法,然后把结果重新入栈,与iadd一样。
执行偏移地址为16的指令,ireturn指令是方法返回指令之一,它将结束方法执行并将操作数栈顶的整型值返回给该方法的调用者。
领取专属 10元无门槛券
私享最新 技术干货