一、概述
棧幀位置
JVM 執行 Java 程序時需要裝載各種數據到內存中,不同的數據存放在不同的內存區中(邏輯上),這些數據內存區稱作運行時數據區(Run-Time Data Areas)。
其中 JVM Stack(Stack 或虛擬機棧、線程棧、棧)中存放的就是 Stack Frame(Frame 或棧幀、方法棧)。
對應關系
一個線程對應一個 JVM Stack。JVM Stack 中包含一組 Stack Frame。線程每調用一個方法就對應着 JVM Stack 中 Stack Frame 的入棧,方法執行完畢或者異常終止對應着出棧(銷毀)。
當 JVM 調用一個 Java 方法時,它從對應類的類型信息中得到此方法的局部變量區和操作數棧的大小,並據此分配棧幀內存,然后壓入 JVM 棧中。
在活動線程中,只有位於棧頂的棧幀才是有效的,稱為當前棧幀,與這個棧幀相關聯的方法稱為當前方法。
棧幀結構
一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。
局部變量表(Local Variable Table)
- 在編譯程序代碼的時候就可以確定棧幀中需要多大的局部變量表,具體大小可在編譯后的 Class 文件中看到。
- 局部變量表的容量以 Variable Slot(變量槽)為最小單位,每個變量槽都可以存儲 32 位長度的內存空間。
- 在方法執行時,虛擬機使用局部變量表完成參數值到參數變量列表的傳遞過程的,如果執行的是實例方法,那局部變量表中第 0 位索引的 Slot 默認是用於傳遞方法所屬對象實例的引用(在方法中可以通過關鍵字 this 來訪問到這個隱含的參數)。
- 其余參數則按照參數表順序排列,占用從 1 開始的局部變量 Slot。
- 基本類型數據以及引用和 returnAddress(返回地址)占用一個變量槽,long 和 double 需要兩個。
操作數棧(Operand Stack)
- 同樣也可以在編譯期確定大小。
- Frame 被創建時,操作棧是空的。操作棧的每個項可以存放 JVM 的各種類型數據,其中 long 和 double 類型(64位數據)占用兩個棧深。
- 方法執行的過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是出棧和入棧操作(與 Java 棧中棧幀操作類似)。
- 操作棧調用其它有返回結果的方法時,會把結果 push 到棧上(通過操作數棧來進行參數傳遞)。
動態鏈接(Dynamic Linking)
- 每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態鏈接。
- 在類加載階段中的解析階段會將符號引用轉為直接引用,這種轉化也稱為靜態解析。另外的一部分將在運行時轉化為直接引用,這部分稱為動態鏈接。
返回地址(Return Address)
- 方法開始執行后,只有 2 種方式可以退出 :方法返回指令,異常退出。
幀數據區(Stack Data)
- 幀數據區的大小依賴於 JVM 的具體實現。
二、反編譯代碼
源代碼

package com.jvm; /** * 編譯:javac com\jvm\StackFrame.java * 反編譯:javap -p -v com\jvm\StackFrame.class */ public class StackFrame { public static void main(String[] args) { add(1, 2); } private static int add(int a, int b) { int c = 0; c = a + b; return c; } }
反編譯后的字節碼(去除了不相關字節碼)
{ public com.jvm.StackFrame(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iconst_1 1: iconst_2 2: invokestatic #2 // Method add:(II)I 5: pop 6: return LineNumberTable: line 9: 0 line 10: 6 private static int add(int, int); descriptor: (II)I flags: ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=3, args_size=2 0: iconst_0 1: istore_2 2: iload_0 3: iload_1 4: iadd 5: istore_2 6: iload_2 7: ireturn LineNumberTable: line 13: 0 line 14: 2 line 15: 6 }
三、字節碼解釋
主要看 add 方法
# 方法描述 # 括號內為入數類型,這里為兩個 int 型入參 # 括號外為返回類型,這里為返回 int 型 descriptor: (II)I # 方法類型,這里為私有的靜態方法 flags: ACC_PRIVATE, ACC_STATIC # 操作數棧為 2 # 本地變量容量為 3 # 入參個數為 2 stack=2, locals=3, args_size=2
執行 add(1,2) 的過程,最后 ireturn 會將操作數棧棧頂的值返回給調用者
LineNumberTable 為代碼行號與字節碼行號的對應關系
https://blog.csdn.net/qian520ao/article/details/79118474
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5