_StriveG Blog

jvm字节码执行引擎

前言

深入理解java虚拟机

从概念模型的角度来说,虚拟机的的方法调用和字节码执行。

运行时帧栈结构

帧栈是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息,每一个方法从调用到返回,都是入栈和出栈的过程。

在编译程序代码的时候,帧栈中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并写入到方法表的Code属性之中,因此,一个帧栈需要的内存不会受运行期变量数据的影响。

局部变量表

用于存放方法参数和方法内部定义的局部变量。在编译为Class文件时,经在方法的Code属性的max_locals数据项中确定了该方法需要分配的局部变量表的最大容量。

基本单位:槽 Slot
存放数据:boolean、byte、char、short、int、float、reference、returnAddress

Slot可以存放32位,如果需要64位,则由两个Slot存放。

虚拟机通过索引定位的方式访问局部变量表,索引从0开始,

在方法执行时,虚拟机是使用局部变量表完成参数变量列表的传递过程,如果是实例方法,那么局部变量表中的每0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问这个隐含的参数,其余参数则按照参数列表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域来分配其余的Slot。局部变量表中的Slot是可重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法,如果当前字节码PC计算器的值已经超出了某个变量的作用域,那么这个变量对应的Slot就可以交给其它变量使用。

Slot会影响GC,例子就不举了,直接说结果。局部变量表还有对象的引用的话,就会影响.

操作数栈

操作数栈的最大深度在编译时写入Code属性的max_stacks中。

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。

方法的返回地址

有两种情况会退出:

  • 执行引擎遇到返回的字节码指令
  • 遇到异常

在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。方法退出的过程实际上等同于把当前栈帧出栈。

方法调用

方法调用阶段就是确定应该调用哪一个方法。

解析

调用目标在程序代码写好、编译器进行编译时就必须确定下来,称为解析。主要包括静态方法和私有方法两大类,对应的字节码指令如下:

  • invokestatic 调用静态方法
  • invokespecial 调用实例构造器方法、私有方法和父类方法
  • invokevirtual 调用所有虚方法
  • invokeinterface 调用接口方法

分派

确定调用方法是哪一个的过程。

  • 静态分配 调用静态方法,编译期无法知道是何种类型,这时会调用他父类的这个方法。
  • 动态分配 和重写有关(@override),会根据实际类型来选择方法。

总结

这一章难度比较大,还有一些地方没理解到,未完,待续。

参考资料

来自经典好书 深入理解java虚拟机。建议入手一本,

最近访客