虛擬機棧
棧管運行,堆管存儲
即:棧解決程序的運行問題,即程序如何執(zhí)行,或者說如何處理數據。堆解決的是數據存儲的問題,即數據怎么放、放在哪兒。
背景:
由于跨平臺性的設計,Java的指令都是根據棧來設計的。不同平臺CPU架構不同,所以不能設計為基于寄存器的。
優(yōu)點是跨平臺,指令集小,編譯器容易實現(xiàn),缺點是性能下降,實現(xiàn)同樣的功能需要更多的指令。
Java虛擬機棧是什么?
Java虛擬機棧(Java Virtual Machine Stack) ,早期也叫Java棧。每個線程在創(chuàng)建時都會創(chuàng)建一-個虛擬機棧,其內部保存一個個的棧幀(Stack Frame) ,對應著一次次的Java方法調用。
?是線程私有的
生命周期
生命周期和線程一致。
作用
主管Java程序的運行,它保存方法的局部變量、部分結果,并參與方法的調用和返回。
棧的特點(優(yōu)點)
●?棧是一種快速有效的分配存儲方式,訪問速度僅次于程序計數器。
●?JVM直接對Java棧的操作只有兩個:
???每個方法執(zhí)行,伴隨著進棧(入棧、壓棧)
???執(zhí)行結束后的出棧工作
●?對于棧來說不存在垃圾回收問題
棧中可能出現(xiàn)的異常
●Java虛擬機規(guī)范允許Java棧的大小是動態(tài)的或者是固定不變的。
???如果采用固定大小的Java虛擬機棧,那每一個線程的Java虛擬機棧容量可以在線程創(chuàng)建的時候獨立選定。如果線程請求分配的棧容量超過Java虛擬機棧允許的最大容量,Java 虛擬機將會拋出一個StackOverflowError異常。
???如果Java虛擬機棧可以動態(tài)擴展,并且在嘗試擴展的時候無法申請到足夠的內存,或者在創(chuàng)建新的線程時沒有足夠的內存去創(chuàng)建對應的虛擬機棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。
棧中存儲的是什么?
●?每個線程都有自己的棧,棧中的數據都是以棧幀(Stack Frame)的格式存在。
●?在這個線程上正在執(zhí)行的每個方法都各自對應一個棧幀(Stack Frame) 。
●?棧幀是一個內存區(qū)塊,是一個數據集,維系著方法執(zhí)行過程中的各種數據信息。
棧的運行原理
●?JVM直接對Java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循“先進后出”/“后進先出”原則。
●?在一條活動線程中,一個時間點上,只會有一個活動的棧幀。即只有當前正在執(zhí)行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為當前棧幀(Current Frame) ,與當前棧幀相對應的方法就是當前方法(Current Method),定義這個方法的類就是當前類(Current Class) 。
執(zhí)行引擎運行的所有字節(jié)碼指令只針對當前棧幀進行操作。
●?如果在該方法中調用了其他方法,對應的新的棧幀會被創(chuàng)建出來,放在棧的頂端,成為新的當前幀。
●?不同線程中所包含的棧幀是不允許存在相互引用的,即不可能在一個棧幀之中引用另外一個線程的棧幀。
●?如果當前方法調用了其他方法,方法返回之際,當前棧幀會傳回此方法的執(zhí)行結果給前一個棧幀,接著,虛擬機會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀。
●?Java方法有兩種返回函數的方式,一種是正常的函數返回,使用return指令、另外一種是拋出異常。。
相關面試題目
●?舉例棧溢出的情況?
???StackOverflowError,通過-Xss設置棧的大小,
●?調整棧大小,就能保證不出現(xiàn)溢出嗎?
???不能,理論上來說死循環(huán)只能讓StackOverflowError出現(xiàn)的更晚一些,就像給你100塊錢和1000塊錢。只能保證你多用點兒時間
●?分配的棧內存越大越好嗎?
???并不是,越大能降低一定時間內出現(xiàn)StackOverflowError的概率,內存空間是有限的,越大會擠占其他空間,會導致線程數變少
●?垃圾回收是否會涉及到虛擬機棧?
???不會,存在Error,不存在GC,就是入棧出棧
●?方法中定義的局部變量是否線程安全?
???具體問題具體分析,如果實在方法內創(chuàng)建的是線程安全的,如果是作為參數傳入的可能就是線程不安全的,如果內部定義,返回出去的話,也是有可能是不安全的
棧幀
==可使用idea的jclasslib插件輔助查看==
內部結構
每個棧幀中存儲著:
●?局部變量表(Local Variables)
???局部變量表也被稱之為局部變量數組或本地變量表
???定義為一個數字數組,主要用于存儲方法參數和定義在方法體內的局部變量,這些數據?類型包括各類基本數據類型、對象引用(reference) ,以及returnAddress類型
???由于局部變量表是建立在線程的棧上,是線程的私有數據,因此不存在數據安全問題
???局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數據項中。在方法運行期間是不會改變局部變量表的大小的。
●?操作數棧(Operand Stack) ( 或表達式棧(Expression Stack),后進先出(Last-In-First-Out)),說直白點就是臨時存儲空間
???操作數棧,在方法執(zhí)行過程中,根據字節(jié)碼指令,往棧中寫入數據或提取數據,即入棧(push) /出棧(pop)。
???某些字節(jié)碼指令將值壓入操作數棧,其余的字節(jié)碼指令將操作數取出棧。使用它們后再把結果壓入棧。比如:執(zhí)行復制、交換、求和等操作
???下圖中bipush,iload,iadd都會涉及到操作數棧入棧,istore會涉及到從操作數棧中出棧
public void testAddOperation() {
byte i = 15;
int j = 8;
int k = i + j;
}
●?動態(tài)鏈接(Dynamic Linking) ( 或指向運行時常量池的方法引用)
???每一個棧幀內部都包含一個指向運行時常量池(Constant Pool)中該棧幀所屬方法的引用包含這個引用的目的就是為了支持當前方法的代碼能夠實現(xiàn)動態(tài)鏈接(Dynamic Linking) 。比如: invokedynamic指 令
???在Java源文件被編譯到字節(jié)碼文件中時,所有的變量和方法引用都作為符號引用( symbolic Reference)保存在class文件的常量池里。比如:描述一個方法調用了另外的其他方法時,就是通過常量池中指向方法的符號引用來表示的,那么動態(tài)鏈接的作用就是為了將這些符號引用轉換為調用方法的直接引用。
●?方法返回地址(Return Address) (或方法正常退 出或者異常退出的定義)
???存放調用該方法的pc寄存器的值。
??? 一個方法的結束,有兩種方式:正常執(zhí)行完成、出現(xiàn)未處理的異常,非正常退出
???無論通過哪種方式退出,在方法退出后都返回到該方法被調用的位置。方法正常退出時,調用者的pc計數器的值作為返回地址,即調用該方法的指令的下一條指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧幀中一般般不會保存這部分信息。
●?一些附加信息
關于slot的理解
●?參數值的存放總是在局部變量數組的index0開始,到數組長度-1的索引結束。
●?局部變量表,最基本的存儲單元是Slot (變量槽)
●?局部變量表中存放編譯期可知的各種基本數據類型(8種),引用類型(reference),returnAddress類 型。
●?在局部變量表里,32位以內的類型只占用一個slot (包括returnAddress類型),64位的類型(long和double)占用兩個slot。
????byte 、short 、char在存儲前被轉換為int,boolqan 也被轉換為int,0表示false,非0表示true。
????long和double 則占據兩個Slot。
●?JVM會為局部變量表中的每一個slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變量表中指定的局部變量值
●?當一個實例方法被調用的時候,它的方法參數和方法體內部定義的局部變量將會按照順序被復制到局部變量表中的每一個slot上
●?如果需要訪問局部變量表中一個64bit的局部變量值時,只需要使用前一個索引即可。(比如:訪問long或double類型變量)
●?如果當前幀是由構造方法或者實例方法創(chuàng)建的,那么該對象引用this將會存放在index為0的slot處,其余的參數按照參數表順序繼續(xù)排列。
補充說明
●?在棧幀中,與性能調優(yōu)關系最為密切的部分就是前面提到的局部變量表。在方法執(zhí)行時,虛擬機使用局部變量表完成方法的傳遞。
●?局部變量表中的變量也是重要的垃圾回收根節(jié)點,只要被局部變量表中直接或間接引用的對象都不會被回收。
Slot的重復利用
●?棧幀中的局部變量表中的槽位是可以重用的,如果一個局部變量過了其作用域,那么在其作用域之后申明的新的局部變量就很有可能會復用過期局部變量的槽位,從而達到節(jié)省資源的目的。
public void test() {
int a = 0;
{
int b = 0;
b = a + 1;
}
// c重復利用了b的slot(槽位)
int c = a + 1;
}