一个Java类的生命周期概括来说需要经过加载、验证、准备、解析以及初始化、使用及卸载的过程。这里不展开加载Class 的过程以及Class文件格式(后期会陆续探讨)。在执行过程中,JVM是如何把Class文件里的字节码转换成我们的虚拟机栈的操作指令,以及整个虚拟机栈的内部数据结构是怎样的,这篇文章后续会详细介绍,并且稍微扩展下JVM规范中的一些字节码指令集。
这篇文章的主要目的还是为了引入后续要介绍的ASM框架的CoreApi 中的Method接口和组件来做一个铺垫。我们知道,Class文件是编译后的以8byte为单位存储的二进制字节流,想要生成和解析一个Class文件,那么我们需要更好地了解在JVM中他是怎样被解析和执行的。整篇主要参考和总结了《Java Virtual Machine SpecificationJavaSE7 Version》以及《ASM 4.0 A Java bytecode engineering library》关于虚拟机执行模型及字节码执行的部分。
一、字节码执行
方法调用在JVM中转换成的是字节码执行,字节码指令执行的数据结构就是栈帧(stack frame)。也就是在虚拟机栈中的栈元素。虚拟机会为每个方法分配一个栈帧,因为虚拟机栈是LIFO(后进先出)的,所以当前线程正在活动的栈帧,也就是栈顶的栈帧,JVM规范中称之为“CurrentFrame”,这个当前栈帧对应的方法就是“CurrentMethod”。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作。
栈帧的数据结构主要分为四个部分:局部变量表、操作数栈、动态链接以及方法返回地址(包括正常调用和异常调用的完成结果)。下面就一一介绍下这四种数据结构。
1、局部变量表(local variables)
当方法被调用时,参数会传递到从0开始的连续的局部变量表的索引位置上。栈帧中局部变量表的长度存储在类或接口的二进制表示中。阅读Class文件会找到Code属性,所以我们能知道local variables的最大长度是在编译期间决定的。一个局部变量表的占用了32位的存储空间(一个存储单位称之为slot,槽),所以可以存储一个boolean、byte、char、short、float、int、refrence和returnAdress数据,long和double需要2个连续的局部变量表来保存,通过较小位置的索引来获取。如果被调用的是实例方法,那么第0个位置存储“this”关键字代表当前实例对象的引用。
2、操作数栈(operand stack)
操作数栈同局部变量表一样,也是编译期间就能决定了其存储空间(最大的单位长度),通过 Code属性存储在类或接口的字节流中。操作数栈也是个LIFO栈。