写在前面
本文参照《深入理解Java虚拟机》写作而成,算是对自己理解JVM解释执行class文件的一篇总结吧。
整体结构
整体结构
我们知道,Java虚拟机栈是线程私有的,也就是一个线程对应一个栈,那么一个线程当然也可以执行多个方法,每一个方法都对应着一个栈帧。
一个线程的方法调用链可能会很长,很多方法都同时处于执行状态,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧关联的方法称为当前方法。
栈帧(stack frame)
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈的栈元素。栈帧中保存了如下几种元素:局部变量表
编译时写入Code属性的max_locals数据项中,即局部变量表的最大容量
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。以slot为最小存储单位,每个solt都应该存放一个boolean,byte,char,shot,int,float,reference或returnAddress类型的数据(64位的long和double占两个slot),其中reference类型表示对一个对象实例的引用,这个引用对虚拟机来说有两个作用:1. 从该引用中直接或者间接的查找对象在Java堆中数据存放的地址索引,2. 通过该引用可以找到该对象的数据类型在方法区所对应的Class对象。
对于实例方法(非static),局部变量表的第0位存储的是this对象。
对于Java中的局部变量,必须先赋值再使用,否则编译将不能通过。
操作数栈
编译时写入Code属性的max_stacks数据项项中,即栈的最大深度。
Java编译器输出的指令流是一种基于栈的指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。
操作数栈中的每一个元素可以是任意的Java类型,32位的栈容量为1,64位栈容量为2。一个方法的执行过程就是一个操作数栈入栈、出栈的过程。
如整数加法字节码指令iadd会取最接近栈顶的两个int元素,然后将这两个int元素相加之后的结果进行入栈。操作数中的数据类型必须和字节码指令严格匹配。
动态链接
每个栈帧中都保存着一个执行运行时常量池中该栈帧所属的方法引用,持有这个引用是为了支持Java中动态绑定这个特性。
我们知道Class文件中包含大量的符号引用,某些符号引用在类加载阶段(具体来说解析阶段)就可以转化为直接引用,这种转化成为静态解析;另一部分将会在每一次运行的时候转化为直接引用,这种转化方式为动态链接。
关于动态连接,我们通过下文的重载和重写来对动态链接进行具体分析。现在只需要知道动态链接有点像类加载阶段的解析阶段所做的事情(变符号引用为直接引用),只不过这个过程发生在程序运行的时候。这也是为什么我们称类加载时解析阶段可以运行在初始化阶段之后了。
方法返回地址
当一个方法开始执行后,只有两种情况可以结束方法的执行。1. JVM执行引擎碰到了任意一个方法返回的字节码指令。2. 在方法的执行过程中碰到了异常。
方法退出的时候,需要做一些操作,这些操作可能包括:恢复上层方法的局部变量表和操作数栈
把方法返回值压入到调用者的操作数栈中
执行后一条指令
作者:fanyank