什么是棧
棧是一種先進(jìn)后出(FILO)的數(shù)據(jù)結(jié)構(gòu)喊递,就像槍的彈夾一樣寞埠,先壓進(jìn)去的子彈是最后打出來(lái)的科平,一般稱之為棧底,而位于“彈夾”最頂端的被稱為棧頂桌肴。
虛擬機(jī)棧解釋
顧名思義虛擬機(jī)棧是JVM中的棧數(shù)據(jù)結(jié)構(gòu),此種數(shù)據(jù)結(jié)構(gòu)是基于線程的宗收,創(chuàng)建了一個(gè)線程就相當(dāng)于創(chuàng)建了一個(gè)虛擬機(jī)棧一汽,例如我們最熟悉的main方法啟動(dòng),就啟動(dòng)了一個(gè)虛擬機(jī)棧碳锈。在線程運(yùn)行的過(guò)程中顽冶,數(shù)據(jù)或者引用被加載到棧中,各種數(shù)據(jù)或者引用會(huì)頻繁的入棧出棧售碳,等到線程消亡强重,虛擬機(jī)棧也跟著被釋放。
虛擬機(jī)棧的大小缺省值為1M贸人,可以用JVM參數(shù)-Xss調(diào)整大小
參考官方文檔: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
搜索:-Xss
可以發(fā)現(xiàn)不同平臺(tái)的缺省值也不同间景,總的來(lái)說(shuō),32位的操作系統(tǒng)對(duì)應(yīng)的是320KB艺智,64位的對(duì)應(yīng)的是1024KB
棧幀
Java中每個(gè)方法都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)倘要,棧幀就是虛擬機(jī)棧的子彈,先創(chuàng)建的先被壓入棧十拣,然后調(diào)用別的方法的時(shí)候又壓入一個(gè)封拧。
例如代碼:
public class StackFrameDemo {
public static void main(String[] args) {
System.out.println(work());
}
public static int work(){
int a = 1;
int b = 1;
int c = a + b;
return c;
}
}
運(yùn)行main方法時(shí)
一個(gè)main方法啟動(dòng)創(chuàng)建了一個(gè)虛擬機(jī)棧,此時(shí)虛擬機(jī)棧中是這樣的:
當(dāng)我們調(diào)用work方法的時(shí)候:
等到work()方法執(zhí)行完夭问,就出棧哮缺,然后main方法出棧,出棧的操作可以看做把子彈射出去甲喝,虛擬機(jī)棧里就沒(méi)了尝苇。
棧幀詳解
棧幀的結(jié)構(gòu)
棧幀結(jié)構(gòu)
一個(gè)棧幀需要分配多少內(nèi)存,不會(huì)受到程序運(yùn)行時(shí)變量數(shù)據(jù)影響埠胖,僅僅取決于虛擬機(jī)的實(shí)現(xiàn)
局部變量表(Local Variable Table)
- 在編譯代碼的時(shí)候就可以確定棧幀需要多大的局部表量表糠溜,具體的大小可以再編譯后的Class文件中看到
- 局部變量表容量以變量槽(Variable Slot)為最小單位,每個(gè)變量槽都可以存儲(chǔ)32位長(zhǎng)度的內(nèi)存空間
- 在方法執(zhí)行的時(shí)候直撤,虛擬機(jī)使用局部變量表完成參數(shù)值到參數(shù)變量表的傳遞過(guò)程非竿,如果執(zhí)行的是實(shí)例方法,那局部表量表的第0個(gè)槽位索引默認(rèn)是用于傳遞方法所屬對(duì)象的引用(this關(guān)鍵字)
- 其余參數(shù)則按照參數(shù)順序排列谋竖,占用從1開始的局部變量表Slot
- 基本數(shù)據(jù)類型以及引用和返回地址(returnAddress)占用一個(gè)變量槽红柱,long和double占兩個(gè)
操作數(shù)棧(Operand Stack)
- 也是在編譯期間就可以確定大小
- 棧幀(Frame)被創(chuàng)建時(shí)承匣,操作數(shù)棧是空的。操作數(shù)棧的每個(gè)定都可以存放JVM各種類型的數(shù)據(jù)锤悄,long和double則占兩個(gè)棧深(和局部變量表是一樣的韧骗,因?yàn)閘ong、double都是64位的數(shù)據(jù))
- 方法執(zhí)行的過(guò)程中零聚,隨著各個(gè)指令的執(zhí)行袍暴,會(huì)有各種數(shù)據(jù)往操作數(shù)棧中寫入和讀取,也就是出棧和入棧操作
- 操作數(shù)棧調(diào)用其他有返回結(jié)果的方法時(shí)隶症,會(huì)把結(jié)果push到棧上(通過(guò)操作數(shù)棧進(jìn)行參數(shù)傳遞)
動(dòng)態(tài)鏈接(Dynamic Linking)
- 動(dòng)態(tài)鏈接涉及到j(luò)ava的多態(tài)特性政模,動(dòng)態(tài)鏈接的說(shuō)明是棧幀內(nèi)部包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,該引用的目的是為了支持動(dòng)態(tài)代理
完成出口(也叫返回地址 Return Address)
- 方法執(zhí)行完成的出口蚂会,只能由2種方式可以退出:return 返回指令淋样,異常退出
- 正常退出的流程是,將調(diào)用方法的返回值(如果有的話)胁住,壓入被調(diào)用方法的操作數(shù)棧頂习蓬,被調(diào)用的方法根據(jù)程序計(jì)數(shù)器繼續(xù)執(zhí)行
通過(guò)字節(jié)碼,查看方法執(zhí)行時(shí)棧幀中的各個(gè)數(shù)據(jù)
-
打開類所在的文件夾措嵌,通過(guò)javac 指令編譯java文件為class字節(jié)碼文件
javac 在通過(guò)javap -c 查看反匯編字節(jié)碼,如果有中文用 javac -encoding UTF-8 XXX.java
work()方法部分的字節(jié)碼為
public static int work();
Code:
0: iconst_1 加載一個(gè)常量1到操作數(shù)棧
1: istore_0 將一個(gè)數(shù)值從操作數(shù)棧加載到局部變量表0的slot
2: iconst_1 加載一個(gè)常量1到操作數(shù)棧
3: istore_1 將一個(gè)數(shù)值從操作數(shù)棧加載到局部變量表1的slot
4: iload_0 將局部變量表0位置的數(shù)值加載到操作數(shù)棧
5: iload_1 將局部變量表1位置的數(shù)值加載到操作數(shù)棧
6: iadd 加法指令
7: istore_2 將一個(gè)數(shù)值從操作數(shù)棧加載到局部變量表2的slot
8: iload_2 將局表變量表位置為2的數(shù)值加載到操作數(shù)棧
9: ireturn 返回躲叼,將操作數(shù)棧頂?shù)姆祷刂祲喝雖ain方法的操作數(shù)棧頂
關(guān)于字節(jié)碼指令的解釋參考博客:https://cloud.tencent.com/developer/article/1333540
用圖形時(shí)間軸看起來(lái)更加明了
這就是work方法執(zhí)行時(shí),內(nèi)存中數(shù)據(jù)的變化企巢。
本地方法棧
與虛擬機(jī)棧類似枫慷,只不過(guò),虛擬機(jī)棧用于管理Java方法的調(diào)用浪规。而本地方法棧則用于管理native方法(例如Thread.start0())的調(diào)用或听,由于native方法不是用Java實(shí)現(xiàn)的,而是由C語(yǔ)言實(shí)現(xiàn)的笋婿。事實(shí)上虛擬機(jī)棧和本地方法棧的區(qū)域是非常類似的誉裆,在Hotspot中直接把本地方法棧和虛擬機(jī)棧合二為一,放在了一個(gè)區(qū)域之中缸濒。當(dāng)然他們本質(zhì)上的是兩個(gè)東西足丢。