棧的定義
棧也是一塊區域,用來存放數據的。棧也叫棧內存,主管Java程序的運行。
棧是私有的,是在線程創建時創建,所以它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放。
因此對于棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命周期和線程一致。
這里把方法放進棧里,說的就是方法在棧幀里面。入棧出棧說的都是棧幀。
棧的特點
- 棧空間也是不需要連續分配,只要在邏輯上相連即可。
- 棧遵循最基本的棧的數據結構特征:先進后出。
- 棧空間支付動態調整大小。-xms
- 棧空間不會出現垃圾回收。(gc作用域不在該區域)
- 棧的 5 棧的創建時機:在線程創建的時候,隨之創建的。
- 棧屬于線程私有區域。
- 對棧的操作,只有入棧出棧(壓棧幀彈棧幀)
存儲什么
棧中未來不是直接存放調用的方法,而是存儲棧幀。棧幀是棧中最小單元。
8種基本類型的變量+對象的引用變量+returnAddress都是在函數的棧內存中分配。
棧幀中存儲:前三個比較重要
- 存儲8種基本數據類型。
- 存儲對象的引用: user未來存儲在棧幀中。
User user = new User();
存輸方法的返回地址(returnAddress)。
-
- 任意一個萬法都要存方法出去后的下一條指令地址,哪怕該方法是 void 類型也得存,因為任意的方法在底層字節碼都會插入一條字節碼指令
return
- 任意一個萬法都要存方法出去后的下一條指令地址,哪怕該方法是 void 類型也得存,因為任意的方法在底層字節碼都會插入一條字節碼指令
public void a() {...b();...
}public void b() {......
}
- 存儲局部變量表(Local Variable)
- 操作數棧。(Operate stack)
- 動態鏈接(抽象)(Dynamic link)
如圖,查看字節碼指令多了return
舉例
當一個方法A被調用時就產生了一個棧幀 F1,并被壓入到棧中,
A方法又調用了 B方法,于是產生棧幀 F2 也被壓入棧,
B方法又調用了 C方法,于是產生棧幀 F3 也被壓入棧,
……
執行完畢后,先彈出F3棧幀,再彈出F2棧幀,再彈出F1棧幀……
遵循“先進后出”或者“后進先出”原則。
圖示在一個棧中有兩個棧幀:
棧幀 2是最先被調用的方法,先入棧,
然后方法 2 又調用了方法1,棧幀 1處于棧頂的位置,
棧幀 2 處于棧底,執行完畢后,依次彈出棧幀 1和棧幀 2,
線程結束,棧釋放。
每執行一個方法都會產生一個棧幀,保存到棧(后進先出)的頂部,頂部棧就是當前的方法,該方法執行完畢后會自動將此棧幀出棧。
常見問題棧溢出:Exception in thread "main" java.lang.StackOverflowError
通常出現在遞歸調用時。
JVM對Java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循“先進后出”或者“后進先出”原則。
一個線程中只能由一個正在執行的方法(當前方法),因此對應只會有一個活動的當前棧幀。
當一個方法1(main方法)被調用時就產生了一個棧幀1 并被壓入到棧中,棧幀1位于棧底位置
方法1又調用了方法2,于是產生棧幀2 也被壓入棧,
方法2又調用了方法3,于是產生棧幀3 也被壓入棧,
…… 執行完畢后,先彈出棧幀4,再彈出棧幀3,再彈出棧幀2,再彈出棧幀1,線程結束,棧釋放。
深入理解
通過在終端中輸入這樣的指令,可以去查看下面要講的內容;或者idea
中的jclasslib插件
javap -verbose 類名.class
局部變量表(Local Variables)
也叫本地變量表。 作用:存儲方法參數和方法體內的局部變量:8種基本類型變量、對象引用(reference)
。 可以用如下方式查看字節碼中一個方法內定義的的局部變量,當程序運行時,這些局部變量會被加載到 局部變量表中。
實例方法第一個存的是this
,方便去調用方法;而類方法直接可以利用類調用,所以就不需要存儲。
局部變量表槽位,一個小方格是一個槽位。
long、double 這種類型是占了兩個小方格
實例方法和形參也都會提前放好
- 報錯時,為什么能知道哪里錯了?行號表;告訴說是哪一行報錯了
操作數棧
作用: 也是一個棧在方法執行過程中根據字節碼指令記錄當前操作的數據,將它們入棧或出棧。用于保存計算過程的中間結果,同時作為計算過程中變量的臨時存儲空間。
- 每一步指令都會嚴格的入棧出棧,想要深究可以去找
pdf
步驟。
動態連接
作用:通過符號引用動態確定某些對象。
比如可以知道當前幀執行的是哪個方法對象。程序在運行期間,通過運行時常量池
中方法的符號引用,找到方法對象。
執行一個方法的流程:(待定了,先看上面解釋即可)
找方法,先得找其類 A,而類在運行時常量池
里,包括各種方法,各種屬性(這里可能是哪個類在運行,其屬性就在里面去找)。
拿到 A 在常量池中的符號 #1, 拿到了符號就可以去常量池拿到了類 A,編譯就不會報錯。
這是靜態鏈接,只在編譯的時候去找。
注意
操作數棧的深度和局部變量表的大小,是在編譯階段確定的。
棧空間溢出
Error Exception 平級,都是繼承了 Throwable 類。
IDEA 中可以修改棧的大小,默認是 1MB。
這里是一直遞歸調用,使得一直使用方法redo
導致棧空間爆炸。
// JVM設置 -Xss128k(默認1M)
public class StatckOverflowTest1 {private static int count = 0;public static void redo(){count++;redo();}public static void main(String[] args) {try {redo();} catch (Throwable e) {e.printStackTrace();System.out.println("counter====="+count);}}
}java.lang.StackOverflowError
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
counter=====10213
查看文檔鏈接