JVM线程栈

JVM栈中存放了一组frame(栈帧),以LIFO的方式存储,方法调用时生成一个frame,方法执行结束时删除一个frame。frame中存放方法调用时调用者的执行状态快照,在被调方法执行结束后,用于恢复方法调用前的程序执行状态。栈帧的概念在c或c++语言编译出来的汇编语言中也存在,用ESP和EBP定义栈顶和栈底。汇编和JVM的栈帧功能都一样,只是汇编的frame直接给cpu看,而JVM里的frame是给JVM看,JVM会在不同硬件设备上实现不同的栈帧逻辑。

c语言在做函数调用时,栈帧的实现会更精细:

1. 在函数调用时,调用者先将入参按照从右到左的顺序,依次推入堆栈。

2. 将函数退出时的指令返回地址推入堆栈(调用者函数调用的下一行命令)。

3. 将当前ebp推入堆栈。

4. 将esp赋值给ebp,这是新栈帧的起点。

5. call命令修改eip,使得程序执行跳转到被调函数,之后在ebp之下存放被调用函数内的局部变量。

6. 函数退出时,使用leave命令,将ebp赋值给esp,被调函数的栈帧失效。

7. ret命令将当前esp的前一个四字节数据推给eip,这是之前存储的返回地址。

8. 函数调用结束,程序继续执行。

9. 函数的返回值通常放在寄存器中传递。

函数调用时的栈使用情况如下图:

JVM线程栈_第1张图片

JVM中的栈帧是抽象的概念,与具体的汇编行为无关。frame由局部变量数组、操作数堆栈、动态链接组成,其中局部变量数据和操作数堆栈的最大深度在编译阶段就要确定,并存储在.class文件里,分别由max_locals和max_stack表示(CodeAttribute),这样frame里的局部变量数组大小才能确定 。对一个线程而言,同一时间只有最新的一帧处于活动状态,新的方法调用会创建新帧,方法正常或异常退出会回收当前帧。

局部变量数组中存放着原始数据类型、对象引用和返回地址(jsr, ret, jsr_w时使用),其中long和double用两个局部变量存储,其它类型用一个。本地变量通过index寻址,index从0开始。方法的入参存放在本地变量数据的开头,其中类方法调用时,index=0处存放着第一个入参。对象方法调用时,index=0处存放this指针,指向调用该方法的对象,index=1处存放第一个入参。

操作数堆栈为线程提供堆栈服务,LIFO,也是数据交换的区域。Java字节码支持从局部变量数组和常量值中加载数据到操作数堆栈,也可以加载对象的成员变量,另外还提供指令处理堆栈里的数据。操作数堆栈就是一张草稿纸。

动态链接是frame的重要组成部分,指向运行时常量池。常量池中存放的符号是字节码运行的必要条件。一些操作数的字节码,其操作数就是常量池中一个编号,动态链接使得这些编号能被转化成实际内容,从而指导字节码调用所需方法,获取实际数值,加载未被加载的类。

 

你可能感兴趣的:(JVM,java)