Java 虛擬機規範定義了字節碼執行的概念模型,具體實現由各厂商決定。本文基於《深入理解 Java 虛擬機》核心知識體系,系統講解運行時棧幀結構、方法調用與分派機制、字節碼解釋執行流程,並配合 javap 反編譯實例剖析。
字節碼是 .class 文件中的指令流,每條指令由一字節操作碼 + 零至多字節操作數組成。它是比機器碼更高抽象層級的中間表示,實現「一次編譯,到處運行」。
每個線程私有虛擬機棧,棧中元素為棧幀。每個方法調用對應一個棧幀入棧→出棧過程。棧幀包含:
this 引用(實例方法)long/double 佔 2 Slotiadd 彈出兩 int 相加,結果壓回Code 屬性 max_stack)行號表(LineNumberTable)、局部變量表(LocalVariableTable)供調試器使用。
方法調用階段只確定調用哪個方法,不執行方法體。JVM 提供 5 條調用指令:
| 指令 | 用途 | 綁定類型 |
|---|---|---|
invokestatic |
靜態方法 | 靜態解析 |
invokespecial |
構造器、私有方法、父類方法 | 靜態解析 |
invokevirtual |
實例方法(虛方法) | 動態分派 |
invokeinterface |
接口方法 | 動態分派 |
invokedynamic |
動態語言支持 | 運行期動態解析 |
針對 invokestatic/invokespecial,目標方法在類加載解析階段即可唯一確定(最終方法、私有方法、構造器、靜態方法)。
核心區別:靜態分派(重載)vs 動態分派(重寫)
class Human {} class Man extends Human {} class Woman extends Human {}
void sayHello(Human h) { println("guy"); }
void sayHello(Man m) { println("gentleman"); }
void sayHello(Woman w) { println("lady"); }
Human man = new Man();
sayHello(man); // 輸出 "guy" - 靜態類型 Human 決定調用 sayHello(Human)
sayHello((Man)man) → "gentleman"abstract class Human { abstract void sayHello(); }
class Man extends Human { void sayHello() { println("gentleman"); } }
class Woman extends Human { void sayHello() { println("lady"); } }
Human man = new Man();
man.sayHello(); // "gentleman" - 運行期根據實際類型 Man 分派
man = new Woman();
man.sayHello(); // "lady" - 同一調用點,不同實際類型,不同目標方法
invokevirtual 指令運行期解析流程:
Objectclass QQ {} class _360 {}
class Father { void hard(QQ q) {} void hard(_360 s) {} }
class Son extends Father { void hard(QQ q) {} void hard(_360 s) {} }
Father f = new Father(); f.hard(new _360()); // Father.hard(_360) - 靜態分派
Father s = new Son(); s.hard(new QQ()); // Son.hard(QQ) - 動態分派(接收者) + 靜態分派(參數)
Java 是靜態多分派、動態單分派語言。
JDK 7 引入 invokedynamic + MethodHandle,支持運行期動態定義方法調用邏輯,為 Lambda、動態語言(Groovy/JRuby)奠基。MethodHandle 是類型安全的直接方法引用,比反射更輕量、可被 JIT 優化。
JVM 採用基於棧的指令集:操作數棧作為運算工作區,指令隱式彈壓棧。
int calc() { int a=100,b=200,c=300; return (a+b)*c; }// 字節碼
0: bipush 100
2: istore_1 // a=100
3: sipush 200
6: istore_2 // b=200
7: sipush 300
10: istore_3 // c=300
11: iload_1
12: iload_2
13: iadd // a+b
14: iload_3
15: imul // (a+b)*c
16: ireturn
bipush 100 → 操作數棧 [100]istore_1 → 彈出 100 存入局部變量表[1]iload_1 → 壓入 100 → 棧 [100]iload_2 → 壓入 200 → 棧 [100, 200]iadd → 彈出 200,100 → 相加 300 → 壓入 → 棧 [300]iload_3 → 壓入 300 → 棧 [300, 300]imul → 彈出 300,300 → 相乘 90000 → 壓入 → 棧 [90000]ireturn → 彈出 90000 返回給調用者理解執行引擎原理,能讓你從字節碼層面優化代碼、診斷性能瓶頸、掌握動態代理與 AOP 底層機制。
