《深入理解java虚拟机》学习笔记(5)--虚拟机字节码执行引擎

运行时栈帧结构  
栈帧是支持虚拟机进行方法调用和执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素。存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。 

在编译时,一个栈帧需要分配多少内存就完全确定了。只有当前栈帧是有效的。 每个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。
1.局部变量表 
  • 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
  • 在Java程序被编译为Class文件时,方法的局部变量表的最大容量就已经确定。
  • 局部变量表以变量槽(Slot)存放数据,变量槽有索引,从0开始。0是当前对象的引用,用this关键字可以获取。其余参数则按照参数列表顺序排列,占用从1开始的局部变量slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的slot(即剩下的slot中存放局部变量)。虚拟机通过slot索引取值。
  • 在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程。
  • 局部变量不像类变量,不会赋初始值,没赋值过就不能使用。
2.操作数栈
  • 它是一个后入先出栈;最大深度在Java程序编译成Class文件时写入到了Code属性的max_stacks中。
  • 比如整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。
  • 操作数栈中元素的类型必须与字节码指令的序列严格匹配。
  • Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的栈就是操作数栈;
3.动态连接
  • 每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking);
  • 字节码中的方法调用指令以指向常量池中方法的符号引用为参数。这些符号引用有一部分在类加载阶段或者第一次使用时就转换为直接引用,这种称为静态解析,而另外一部分在每一次运行期间转换为直接引用,这部分称为动态连接;
4.方法返回地址
  • 退出方法的方式:正常完成出口和异常完成出口;
  • 方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能只需的操作有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
综合以上,可以得到下图:
方法调用
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本即调用哪一个方法,暂时还不涉及方法内部的具体运行过程;
Class文件的编译过程中不包含传统编译的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局的入口地址。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂。方法调用的目的就是将符号引用转化为直接引用。
解析
在类加载的解析阶段,会将一部分符号引用转化为直接引用。这种解析能成立的前提是 方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。也就是说,被调用的方法在编译阶段就可以确定下来。这类方法的调用称为解析。

在Java语言中符合编译器可知、运行期不可变这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能被改变,适合在类加载阶段进行解析。

Java虚拟机提供了5条方法调用指令:
  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的.
只要能被 invokestaticinvokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为直接引用。

解析调用是一个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成;而分派调用则可能是静态的也可能是动态的。
分派
静态分派
所有依赖静态类型来定位方法执行版本的分派动作成为静态分派。典型应用是 方法重载 。静态分派发生在编译阶段,因此不是由虚拟机来执行的。
动态分派
在运行期根据实际类型确定方法执行版本的分派过程成为动态分派。典型应用是 方法重写
invokevirtual指令 执行过程:
(1)找到方法接收者(即this对象)的实际类型;
(2)在类中查找所调用的方法,找到了,则返回这个方法的直接引用;
(3)否则按照继承关系自下而上依次在父类中查找。
单分派与多分派
方法的接收者与方法的参数统称为方法的宗量。根据分派基于多少种宗量,可以将分派分为单分派和多分派两种。单分派是根据一个宗量对目标方法进行选择,而多分派是根据多于一个宗量对目标方法进行选择。今天的Java语言是一门静态多分派、动态单分派的语言。
虚拟机动态分派的实现
虚拟机为每个类在 在方法区中建立一个虚方法表(Virtual Method Table,也秒vtable,与此相对应在,在invokeinterface执行也会用到接口方法表----Interface Method Table,简称itable),使用虚方法表索引来代替元数据查找以提高性能。

虚方法表中表项的内容指向各个方法的实际入口。如果某个方法在子类中没有被重写,那么在子类的虚方法表中相应项仍然指向父类方法的实际入口。为了程序实现上的方便,具有相同签名的方法,在父类和子类的虚方法表中都具有一样的索引序号

方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始化值后,虚拟机会把该类的方法表也初始化完毕。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值