什么是栈帧?)格式存在。方法和堆栈帧之间的关系是什么?在这个线程上执行的每个方法都对应一个栈帧(StackFrame)。栈帧是一个内存块,是一个数据集,在方法执行过程中维护着各种数据信息。栈帧JVM进阶后厨的FILO原理,对Java栈的直接操作只有两个:每个方法执行,执行完成后伴随入栈(stack,push),pop工作紧随其后"firstinlastout"/"栈帧的内部结构按照"后进先出"的原则。方法体中定义的参数和局部变量,这些数据类型包括各种基本数据类型(8种),对象引用(reference),和returnAddress类型。局部变量表所需的容量在编译时确定,并存储在方法的Code属性的最大局部变量数据项中。局部变量表的大小是不变的方法执行过程中,方法嵌套调用的次数由栈的大小决定,一般来说,栈越大,方法调用的嵌套越多。局部变量表会越大,它的栈帧也会越大,以满足对方法调用传递的信息越来越多的需求。反过来,函数调用会占用更多的堆栈空间,导致嵌套调用的数量减少。局部变量表中的变量只在当前方法调用中有效。方法执行时,虚拟机通过局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束时,随着方法栈帧的销毁,局部变量表也会被销毁。槽参数值在局部变量表中的存储总是从局部变量数组的索引0开始,到数组长度-1的索引结束。在局部变量表中,最基本的存储单元是Slot(变量槽)。在局部变量表中,32位以内的类型只占用一个槽位(包括returnAddress类型),64位的类型(long和double)占用两个槽位。byte、short、char存储前都转成int,boolean也转成int,0表示false,非0表示true。long和double占用两个Slots。JVM会为局部变量表中的每个Slot分配一个访问索引,通过该索引可以成功访问局部变量表中指定的局部变量值。当一个实例方法被调用时,它的方法参数和方法体内部定义的局部变量会被依次复制到局部变量表中的每个Slot中。如果需要在局部变量表中访问一个64bit的局部变量值,只需要使用前面的索引即可。(例如:访问long或double类型的变量)如果当前帧是由构造函数或实例方法创建的,那么对象引用this会存放在索引为0的槽中,其余的参数会继续按照参数列表的顺序排列。插槽可以重复使用吗?栈帧中局部变量表中的槽位可以重复使用。如果一个局部变量已经超过了它的作用域,那么在它的作用域之后声明的新局部变量很有可能会重用过期局部变量的槽位,以节省资源。公共类SlotTest{publicvoidlocalVarl(){inta=0;System.out.println(a);整数b=0;}publicvoidlocalVar2(){{inta=0;System.out.println(a);}//此时b会复用a的slotintb=0;}静态变量和局部变量的比较参数表分配后,按照方法体中定义的变量的顺序和作用域进行分配。我们知道类变量表有两次初始化的机会,第一次是在“准备阶段”,进行系统初始化,将类变量置零值,第二次是在“初始化”阶段,这是给程序员在代码中定义的初始值。与类变量初始化不同的是,局部变量表没有系统的初始化过程,也就是说局部变量一旦定义,就必须手动初始化,否则无法使用。publicvoidtest(){inti;System.out.println(i);}这样的代码是错误的,没有赋值就不能使用。操作数栈的概念我们说Java虚拟机的解释引擎是基于栈的执行引擎,栈就是指操作数栈。每个独立的栈帧除了局部变量表外,还包含一个后进先出(Last-In-First-Out)操作数栈,也可以称为表达式栈(ExpressionStack)。操作数栈是JVM执行引擎的一个工作区域。当一个方法开始执行时,会相应地创建一个新的栈帧。该方法的操作数栈为空。每个操作数栈都会有一个明确的栈深度来存储值。所需的最大深度在编译时定义并存储在方法的代码属性中,即max_stack的值。堆栈中的任何元素都可以是任何Java数据类型。32bit类型占用一个栈单元深度,64bit类型占用两个栈单元深度操作数栈。在方法执行过程中,根据字节码指令,数据访问不是通过访问索引来进行的,而只是通过标准的入栈(push)和入栈(pop)操作,将数据写入栈或者提取数据来完成一次数据访问。一些字节码指令将值压入操作数栈,另一些则将操作数弹出栈。使用它们后,将结果压入堆栈。例如:执行复制、交换、求和等操作,如果被调用的方法有返回值,则将返回值压入当前栈帧的操作数栈,PC中下一个要执行的字注册将被更新。节代码说明。代码和字节码演示publicvoidtestAddOperation(){bytei=15;整数j=8;intk=i+j;}字节码如下栈顶缓存技术前面提到,基于栈架构的虚拟机使用的零地址指令更加紧凑,但是需要使用更多的push和pop指令完成一个操作,这也意味着需要更多的指令调度时间和内存读/写操作。写次。由于操作数存储在内存中,频繁执行内存读写操作必然会影响执行速度。为了解决这个问题,HotSpotJVM的设计者提出了栈顶缓存(ToS,Top-of-StackCashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,这样从而减少内存的读写次数,提高执行引擎的执行效率。动态链接(或对运行时常量池的方法引用)每个栈帧都包含对栈帧所属的运行时常量池中方法的引用。包含这个引用的目的是为了支持当前方法的代码实现动态链接(DynamicLinking)。例如:当invokedynamic指令从Java源文件编译成字节码文件时,所有的变量和方法引用都作为符号引用(SymbolicReference)存储在类文件的常量池中。例如:当描述一个方法调用另一个方法时,在常量池中用一个指向该方法的符号引用来表示,那么动态链接的作用就是将这些符号引用转化为对调用方法的直接引用。publicvoidtestGetSum(){inti=getSum();intj=10;}为什么我们需要常量池?常量池的作用是提供一些符号和常量,方便指令的识别。方法返回地址存储调用该方法的pc寄存器的值。结束一个方法有两种方式:正常执行完成发生未处理的异常,异常退出无论哪个方法退出,方法退出后都会回到调用方法的地方。当方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令后的下一条指令的地址。对于异常退出的,必须通过异常表来确定返回地址,这部分信息一般不会保存在栈帧中。栈帧中也允许携带一些与Java虚拟机实现相关的附加信息。例如,为程序调试提供支持的信息。连续三个码字好不容易求点赞收藏~
