什么是栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack) [1]的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
结构图
- 局部变量表
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,存在方法的Code属性的Max_locals数据项中。 - 操作数栈
操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。 - 动态连接
每个栈帧都包含一个指向运行时常量池 [1]中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。 - 方法返回地址
小总结
总结一下上面JVM里面运行时栈帧的情况,下面我开下脑洞,我想看一下c语言在编译的时候是怎么创建栈帧的。
类比C语言的编译过程中的一些情况
编译过程
gcc这个是驱动程序,用来安排编译的文件情况。
编译的过程大概如下图:预编译(文件包含,宏定义、条件编译)-编译-汇编-连接
编译一个简单的文件xun.c
root@miv:~/learn# cat xun.c
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
root@miv:~/learn# gcc -v xun.c
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
...
预编译、编译的阶段出现.cfi指令
使用命令:gcc -S my_c_file
发现有一些.cfi指令
root@miv:~/learn# gcc -S xun.c
root@miv:~/learn# ls
a.out xun.c xun.i xun.s
root@miv:~/learn# cat xun.s
.file "xun.c"
.text
.section .rodata
.LC0:
.string "Hello, World! "
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
".cfi"开头的伪指令是辅助汇编器创建栈帧(stack frame)信息的。还有一部分是伪指令,
伪指令是不参与CPU运行的,只指导编译链接过程。
栈帧结构图大概如下
gcc命令
- 编译、汇编、连接
gcc -v my_c_file - 预编译
gcc -E my_c_file -o my_c_file.i - 预编译、编译
gcc -S my_c_file
总结
其实上面第一个角度是从Java的JVM角度看栈帧,第二个角度是分析c语言编译过程中的一个情况,这里涉及到方法调用,那么会有一些辅助的代码创建栈帧。
还没去看虚拟机源码,暂时就这可以通过这个角度看下,扩展一些知识面。
参考
《深度探索Linux操作系统》
《深入理解Java虚拟机》