Java虚拟机之栈帧

写在前面的话:Java虚拟机是一门学问,是众多Java大神们的杰作,由于我个人水平有限,精力有限,不能保证所有的东西都是正确的,这里内容都是经过深思熟虑的,部分引用原著的内容,讲的已经很好了,不在累述。当然在这里,不可能所有细节都深层次的分析,只讲到一些比较重要的概念,由于对计算机组成原理理解不深,绝大部分只能采取黑盒理论来分析。
 
运行时的栈帧结构(什么是栈帧?)
    栈帧是虚拟机进行调用和方法执行的数据结构,简单的说栈帧其实就是JVM运行时数据区虚拟机机栈(JVM Stack)的栈元素,,,每一个方法的执行和调用对应着一个栈帧。举个简单的例子,定义一个Stack ,这个Statck 中放入一些叫做栈帧的对象,这个对象中包含了局部变量表,操作数栈,动态链接和方法返回地址等属性。下面,我们讲一下栈帧这个对象的结构。
 
      首先应当明白,一个线程中一个方法的调用链可能会很长,当中有很多方法都处在执行状态,对于我们的执行引擎来说,只有位于栈顶的栈帧才是有效的,这个叫做当前栈帧(Concurrent Satck Frame),与这个栈帧相关的方法称作当前方法(Concurrent Method),相应的概念模型如下:
    Java虚拟机之栈帧Java虚拟机之栈帧
                                 栈帧的概念模型
 
通过栈帧的概念模型,接下来说说,栈帧这个对象的相关属性,有什么作用?数据结构是怎样的
 
        1.局部变量表(Local Variable Table)
 
  它是一组变量的存储空间,用于存放方法上和方法内部的变量。在Java编译期就已完成局部变量表的最大容量分配,说的直白点,局部变量表就是存储局部变量的表,用来存储变量用的;其中它的容量用变量槽(Variable Slot,简称Slot)来衡量,Slot也是最小单位;相关资料参考周志明《深入理解Java虚拟机 2版》P238;以下是相关类型的数据所占用的内存空间
 
Java虚拟机之栈帧Java虚拟机之栈帧
从图中可以看出,基本数据类型除了double和long分割成2个32位(也就是2个Slot)进行存储以外,即高位对齐方式;而其他类型都只占用1个32位的Slot;另外
引用类型可能是32位也可能是64位,Java中没有明确规定。那么虚拟机又是怎样访问局部变量的呢?
 
虚拟机通过索引定位法的方式使用局部变量表,索引值的范围是从0到Slot的最大数量。在方法执行时,特别是执行实例方法时,那么实例变量表的第0位索引默认是方法所属的实例对象的引用“this”对象,接着是1到Slot参数变量到方法内部的局部变量。另外为了节省栈帧空间,局部变量的Slot是可以复用的,也就是说方法参数+方法内局部变量 !=最大Slot数。由于Slot可以复用,不仅节省了空间的开销,同时也对系统的垃圾回收起到意想不到的作用。参考P239
 
 
        2.操作数栈(Operand Stack)
 
  操作数栈是一个后入先出的栈(LIFO) ,基本原理和存储方式和局部变量变一样,32位的数据类型用的栈的容量大小是1,64位的就是2;方法执行的任何时候,操作数栈的深度都不会超过在max_statcks数据项中设置的最大值。参考P242,以下做个总结
1.栈桢刚创建时,里面的操作数栈是空的。
2.Java虚拟机提供指令来让操作数栈对一些数据进行入栈操作,比如可以把局部变量表里的数据、实例的字段等数据入栈。
3.同时也有指令来支持出栈操作。
4.向其他方法传参的参数,也存在操作数栈中。
5.其他方法返回的结果,返回时存在操作数栈中。
6.栈帧中的部分操作数栈和上一个栈帧的局部变量变存在一定的重叠,主要是为了共享数据而存在。
 
       3.动态连接(Dynamic Linking)
 
  每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的 符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化 称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
 
  4.方法返回地址
 
  当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调 用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。
另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用 athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。
无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上 层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是 要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。
方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。
 
   5.附加信息
 
  虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。
 
注:相关信息引自周志明《深入理解Java虚拟机 2版》,仅学习分享,不做商业用途!
上一篇:在Ubuntu12.04上安装图形化配置与window共享的samba服务器


下一篇:MFC多国语言——配置文件