「运行时数据区」——程序计数器、虚拟机栈( 五 )


基于栈式架构的虚拟机所使用的零地址指令更加紧凑 , 但完成一项操作的时候必然需要使用更多的入栈和出栈指令 , 这同时也就意味着将需要更多的指令分派(instruction dispatch)次数和内存读/写次数 。
由于操作数是存储在内存中的 , 因此频繁地执行内存读/写操作必然会影响执行速度 。 为了解决这个问题 , HotSpot JVM的设计者们提出了栈顶缓存(Tos , Top-of-Stack Cashing)技术 ,将栈顶元素全部缓存在物理CPU的寄存器中 , 以此降低对内存的读/写次数 , 提升执行引擎的执行效率。
3.5 动态链接每一个栈帧内部都包含一个指向 运行时常量池中该栈帧所属方法的引用。 包含这个引用的目的就是为了支持当前方法的代码能够实现 动态链接(Dynamic Linking)。 比如invokedynamic指令 。
在Java源文件被编译到字节码文件中时 , 所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里 。 比如:描述一个方法调用了另外的其他方法时 , 就是通过常量池中指向方法的符号引用来表示的 , 那么 动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
「运行时数据区」——程序计数器、虚拟机栈文章插图
常量池的作用:就是为了提供一些符号和常量 , 便于指令的识别 。
3.6 方法返回地址当一个方法开始执行后 , 只有两种方式可以退出这个方法:

  • 正常执行完成
  • 出现未处理的异常
执行引擎遇到任意一个方法返回的字节码指令(return) , 会有返回值传递给上层的方法调用者 , 简称“ 正常调用完成 ”(Normal Method Invocation Completion):
  • 一个方法在正常调用完成之后 , 究竟需要使用哪一个返回指令 , 还需要根据方法返回值的实际数据类型而定 。
  • 在字节码指令中 , 返回指令包含ireturn(当返回值是boolean , byte , char , short和int类型时使用) , lreturn(Long类型) , freturn(Float类型) , dreturn(Double类型) , areturn 。 另外还有一个return指令声明为void的方法 , 实例初始化方法 , 类和接口的初始化方法使用 。
在方法执行过程中遇到异常(Exception) , 并且这个异常没有在方法内进行处理 , 也就是只要在本方法的异常表中没有搜索到匹配的异常处理器 , 就会导致方法退出 , 这种退出方法的方式称为“ 异常调用完成 “(Abrupt MethodInvocation Completion) 。
无论通过哪种方式退出 , 在方法退出后都返回到该方法被调用的位置 。 方法正常退出时 ,调用者的PC寄存器的值作为返回地址 , 即调用该方法的指令的下一条指令的地址。 而通过异常退出的 , 返回地址是要通过异常表来确定 , 栈帧中一般不会保存这部分信息 。
本质上 , 方法的退出就是当前栈帧出栈的过程 。 此时 , 需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等 , 让调用者方法继续执行下去 。
正常完成出口和异常完成出口的区别在于: 通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。
3.7 附加信息《Java虚拟机规范》允许虚拟机实现增加一些规范里没有描述的信息到栈帧之中 , 例如与调试、性能收集相关的信息 , 这部分信息完全取决于具体的虚拟机实现 , 这里不再详述 。
参考深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)
运行时数据区Oracle官网介绍
原文地址:
【「运行时数据区」——程序计数器、虚拟机栈】原文作者:凯宝宝