Java中的棧概述及JVM 中的棧結(jié)構(gòu)
一、什么是棧(Stack)
棧(Stack) 是一種受限的線性數(shù)據(jù)結(jié)構(gòu),只能在一端(稱為 棧頂(Top))進行插入和刪除操作。
它遵循 后進先出(LIFO, Last In First Out) 的原則 —— 后放進去的元素先被取出。
可以把它想象成“疊盤子”的場景:
把盤子一個個疊上去 → 壓棧(push)
取盤子時只能從最上面拿 → 彈棧(pop)
這就是棧的直觀模型。
二、棧的基本操作
| 操作 | 含義 |
|---|---|
| push(x) | 將元素 x 壓入棧頂 |
| pop() | 移除并返回棧頂元素 |
| peek() / top() | 查看棧頂元素但不刪除 |
| isEmpty() | 判斷棧是否為空 |
三、棧的存儲結(jié)構(gòu)
棧可以通過兩種方式實現(xiàn):
順序棧(Array Stack):
使用數(shù)組實現(xiàn),結(jié)構(gòu)簡單、訪問高效。
常用于空間大小可預(yù)估的情況。
JVM 的操作數(shù)棧就是基于數(shù)組實現(xiàn)的。
鏈棧(Linked Stack):
使用鏈表實現(xiàn),插入刪除靈活,不需要預(yù)設(shè)大小。
適合棧深不確定的場景(如深度遞歸)。
四、棧的典型應(yīng)用場景
1. 函數(shù)調(diào)用與返回(Call Stack)
當函數(shù) A 調(diào)用函數(shù) B 時:
函數(shù) A 的局部變量與返回地址會被壓入棧;
JVM 跳轉(zhuǎn)執(zhí)行函數(shù) B;
當 B 執(zhí)行完畢后,棧頂記錄被彈出,返回 A 的調(diào)用點繼續(xù)執(zhí)行。
void A() {
B();
}
void B() {
System.out.println("Hello Stack");
}執(zhí)行流程:
A() 調(diào)用 → JVM 為 A 創(chuàng)建棧幀 → 壓棧 A 調(diào)用 B → JVM 為 B 創(chuàng)建棧幀 → 壓棧 B 執(zhí)行完 → B 的棧幀出棧 → 返回 A A 執(zhí)行完 → A 的棧幀出棧
每個方法調(diào)用都對應(yīng)著 一次入棧與出棧操作。
這就是我們常說的“調(diào)用棧”。
2. 表達式求值與語法解析
棧是編譯器和解釋器處理中綴表達式的關(guān)鍵結(jié)構(gòu)。
例如表達式:
(1 + 2) * 3
求值過程(編譯器利用棧存儲操作符與中間結(jié)果):
(1)讀取 ( → 壓棧(表示新的子表達式)
(2)讀取 1 → 壓棧(操作數(shù))
(3)讀取 + → 壓棧(操作符)
(4)讀取 2 → 壓棧
(5)遇到 ) → 彈出操作符與操作數(shù)計算(得到 3),結(jié)果壓棧
(6)讀取 * → 壓棧
(7)讀取 3 → 壓棧
(8)彈出 * 與兩個操作數(shù)計算(得到 9)
最終結(jié)果為 9。
類似邏輯也用于:
括號匹配校驗(檢查是否“左括號=右括號”)
中綴轉(zhuǎn)后綴(逆波蘭表達式)
編譯器語法樹構(gòu)建
3. 遞歸與回溯(Recursion & Backtracking)
遞歸調(diào)用的本質(zhì),就是函數(shù)不斷地入棧與出棧。
例如計算階乘:
int factorial(int n) {
if (n == 1) return 1;
return n * factorial(n - 1);
}執(zhí)行 factorial(3) 的過程:
factorial(3) → 入棧 factorial(2) → 入棧 factorial(1) → 入棧 factorial(1) 返回 1 → 出棧 factorial(2) 返回 2 * 1 = 2 → 出棧 factorial(3) 返回 3 * 2 = 6 → 出棧
當最內(nèi)層函數(shù)執(zhí)行完畢后,系統(tǒng)會逐層彈棧返回結(jié)果。
這正是遞歸函數(shù)能“自動返回”的根本原因。
4.其他算法中的應(yīng)用
DFS(深度優(yōu)先搜索):利用棧保存待訪問的節(jié)點
括號匹配:用棧判斷表達式是否合法
瀏覽器前進/后退:前進與回退分別用兩個棧保存歷史
撤銷(Undo)操作:通過棧記錄每次修改歷史,支持一步步撤銷
五、棧在 JVM 中的體現(xiàn)
JVM 是一臺 基于棧的虛擬機。
這里的“棧”指的是 每個線程獨有的 JVM 棧(Java Virtual Machine Stack)。
它記錄了方法調(diào)用過程與執(zhí)行狀態(tài),是 Java 程序運行的核心結(jié)構(gòu)。
1.JVM 棧的結(jié)構(gòu)與作用
當線程創(chuàng)建時,JVM 會為其分配一個獨立的 JVM 棧。
每次方法調(diào)用時,都會在這個棧中創(chuàng)建一個 棧幀(Stack Frame)。
每個棧幀包含以下關(guān)鍵部分:
| 組成部分 | 說明 |
|---|---|
| 局部變量表(Local Variables) | 存儲方法參數(shù)與局部變量 |
| 操作數(shù)棧(Operand Stack) | 臨時計算區(qū),用于執(zhí)行字節(jié)碼運算 |
| 動態(tài)鏈接 | 指向運行時常量池中該方法的引用 |
| 方法返回地址 | 方法執(zhí)行完后返回調(diào)用點 |
當方法被調(diào)用時:棧幀入棧
當方法執(zhí)行完畢時:棧幀出棧
每個線程的調(diào)用鏈條就是 JVM 棧幀的入棧與出棧過程。
2. 操作數(shù)棧:JVM 的“計算引擎”
JVM 并不像 CPU 一樣通過寄存器運算,而是依靠 操作數(shù)棧(Operand Stack) 來完成計算。
例如:
int a = 1; int b = 2; int c = a + b;
對應(yīng)字節(jié)碼:
0: iconst_1 // 將常量1壓入棧 1: istore_1 // 彈出1,存入局部變量表槽1 (a) 2: iconst_2 // 壓入常量2 3: istore_2 // 彈出2,存入槽2 (b) 4: iload_1 // 取出a壓入操作數(shù)棧 5: iload_2 // 取出b壓入操作數(shù)棧 6: iadd // 彈出兩個數(shù)相加,結(jié)果壓棧 7: istore_3 // 彈出結(jié)果,存入槽3 (c)
所有運算都通過“壓入棧 → 運算 → 彈出”完成。
這正是 JVM 基于棧結(jié)構(gòu)執(zhí)行指令的直接體現(xiàn)。
3. JVM 調(diào)用棧過程
例如:
public static void main(String[] args) {
foo();
}
public static void foo() {
bar();
}
public static void bar() {}執(zhí)行過程:
| 步驟 | 事件 | 棧狀態(tài)(自下而上) |
|---|---|---|
| ① | main() 開始執(zhí)行 | [main] |
| ② | main 調(diào)用 foo() | [main → foo] |
| ③ | foo 調(diào)用 bar() | [main → foo → bar] |
| ④ | bar 執(zhí)行完畢 | [main → foo] |
| ⑤ | foo 執(zhí)行完畢 | [main] |
| ⑥ | main 執(zhí)行完畢 | [](空棧) |
可以把調(diào)用棧理解為“任務(wù)清單疊疊樂”:
每次調(diào)用方法就是“加一張任務(wù)卡”,執(zhí)行完就“拿掉最上面的那張”。
六、棧與 JVM 的關(guān)系對照表
| 內(nèi)容 | 棧的普通概念 | JVM 中的體現(xiàn) |
|---|---|---|
| 存儲單位 | 元素 | 棧幀(Stack Frame) |
| 操作方式 | push / pop | 方法調(diào)用 → 入棧,方法返回 → 出棧 |
| 訪問原則 | LIFO(后進先出) | 方法的調(diào)用與返回順序 |
| 主要用途 | 表達式計算、遞歸、回溯 | 保存局部變量、執(zhí)行指令、維護調(diào)用鏈 |
| 結(jié)構(gòu)組成 | 棧頂、棧底 | 局部變量表 + 操作數(shù)棧 + 鏈接信息 |
五、總結(jié)
棧(Stack)是一種遵循“后進先出”(LIFO)原則的線性數(shù)據(jù)結(jié)構(gòu),只能在一端進行插入和刪除操作,常用來保存臨時數(shù)據(jù)和控制程序執(zhí)行流程。在計算機中,棧的思想貫穿編譯、運行與算法設(shè)計全過程:在算法中用于遞歸、回溯、括號匹配、撤銷操作等;在編譯器中用于表達式求值和語法解析;在 JVM 中,每個線程都有獨立的 JVM 棧,用來管理方法調(diào)用,通過“棧幀”的入棧與出棧保存局部變量、返回地址和計算過程,操作數(shù)棧則承擔所有計算任務(wù)??梢哉f,棧不僅是數(shù)據(jù)結(jié)構(gòu)中的重要基礎(chǔ),更是理解程序執(zhí)行機制、方法調(diào)用過程和虛擬機底層原理的核心。
到此這篇關(guān)于Java中的棧概述及JVM 中的棧結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)java jvm棧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程實現(xiàn)基于圖的深度優(yōu)先搜索和廣度優(yōu)先搜索完整代碼
這篇文章主要介紹了Java編程實現(xiàn)基于圖的深度優(yōu)先搜索和廣度優(yōu)先搜索完整代碼,具有一定借鑒價值,需要的朋友可以了解下。2017-12-12
Java中JFinal框架動態(tài)切換數(shù)據(jù)庫的方法
這篇文章主要介紹了Java中JFinal框架動態(tài)切換數(shù)據(jù)庫的方法,本文通過兩種方法結(jié)合示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
SpringBoot中整合Ehcache實現(xiàn)熱點數(shù)據(jù)緩存的詳細過程
這篇文章主要介紹了SpringBoot中整合Ehcache實現(xiàn)熱點數(shù)據(jù)緩存,SpringBoot 中使用 Ehcache 比較簡單,只需要簡單配置,說白了還是 Spring Cache 的用法,合理使用緩存機制,可以很好地提高項目的響應(yīng)速度,需要的朋友可以參考下2023-04-04
SpringBoot @value注解動態(tài)刷新問題小結(jié)
@Value注解 所對應(yīng)的數(shù)據(jù)源來自項目的 Environment 中,我們可以將數(shù)據(jù)庫或其他文件中的數(shù)據(jù),加載到項目的 Environment 中,然后 @Value注解 就可以動態(tài)獲取到配置信息了,這篇文章主要介紹了SpringBoot @value注解動態(tài)刷新,需要的朋友可以參考下2023-09-09
Java中HashMap和Hashtable及HashSet的區(qū)別
以下是對Java中HashMap和Hashtable及HashSet的區(qū)別進行了詳細的分析介紹,需要的朋友可以過來參考下2013-09-09
Java JVM字節(jié)碼指令集總結(jié)整理與介紹
本節(jié)將會著重介紹一下JVM中的指令集、Java是如何跨平臺的、JVM指令集參考手冊等內(nèi)容。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09

