什麼是棧幀
棧幀(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虛擬機》