你只知道JVM栈,知不知道栈帧、局部变量表、slot、操作数栈?
目錄
虛擬機棧基礎
虛擬機棧出現的背景
棧是運行時的單位,而堆是存儲的單位
Java虛擬機棧
棧中可能出現的異常
設置Java棧大小
棧中存儲什么?
棧運行原理
棧幀的內部結構
每個棧幀中存儲著
局部變量表(Local Variables)(簡而言之就是方法內部定義的形參、局部變量值、返回值)
代碼示例
關于slot(槽)的理解
Slot的重復利用
靜態變量與局部變量的對比
變量的分類
補充說明
操作數棧(用數組來實現的)
操作數棧+代碼解析
帶返回值的
?i++和++i的區別
棧頂緩存技術
虛擬機?;A
虛擬機棧出現的背景
? ? 由于跨平臺性的設計,Java的指令都是根據棧來設計的。不同平臺CPU架構不同,所以不能設計為基于寄存器的。
? ? 優點是跨平臺、指令集小,編譯器容易實現;缺點是性能下降,實現同樣的功能需要更多的指令。
棧是運行時的單位,而堆是存儲的單位
? ? 即:棧解決程序的運行問題,即程序如何執行,或者說如何處理數據。堆解決的是數據存儲的問題,即數據怎么放、放在哪兒。
Java虛擬機棧
? ? Java虛擬機棧(Java Virtual Machine Stack),早期也叫Java棧。每個線程在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應著一次次的Java方法調用(相當于一個個棧幀的入棧和出棧的操作)。
? ? Java虛擬機棧是線程私有的,生命周期和線程一致。
? ? Java虛擬機棧主管Java程序的運行,它保存方法的局部變量、部分結果,并參與方法的調用和返回。
? ? 棧是一種快速有效的分配存儲方式,訪問速度僅次于程序計數器。
? ? JVM直接對Java棧的操作只有兩個:每個方法執行,伴隨著進棧(入棧、壓棧);執行結束后的出棧操作。
? ? 棧不存在垃圾回收問題,存在內存溢出問題。
棧中可能出現的異常
Java虛擬機規范允許Java棧的大小是動態的或者是固定不變的。
? ? 1.如果采用固定大小的Java虛擬機棧,那每一個線程的Java虛擬機棧容量可以在線程創建的時候獨立選定。如果線程請求分配的棧容量超過Java虛擬機棧允許的最大容量,Java虛擬機將會拋出一個StackOverFlowError異常。
? ? 2.如果Java虛擬機??梢詣討B擴展,并且在嘗試擴展的時候無法申請到足夠的內存,或者在創建新的線程時沒有足夠的內存去創建對應的虛擬機棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。
設置Java棧大小
-Xss 1m
棧中存儲什么?
? ? 每個線程都有自己的棧,棧中的數據都是以棧幀(Stack Frame)的格式存在。
? ? 在這個線程上正在執行的每個方法都各自對應一個棧幀(Stack Frame)。
? ? 棧幀是一個內存區塊,是一個數據集,維系著方法執行過程中的各種數據信息。
棧運行原理
? ? JVM直接對Java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循“先進后出”/“后進先出”原則。
? ? 在一條活動線程中,一個時間點上,只會有一個活動的棧幀。即只有當前正在執行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為當前棧幀(Current Frame),與當前棧幀相對應的方法就是當前方法(Current Method),定義這個方法的類就是當前類(Current Class)。
? ? 執行引擎運行的所有字節碼指令只針對當前棧幀進行操作。
? ? 如果在該方法中調用了其他方法,對應的新的棧幀會被創建出來,放在棧的頂端,成為新的當前幀。
? ? 不同線程中所包含的棧幀是不允許存在相互引用的,即不可能在一個棧幀之中引用另外一個線程的棧幀。
? ? 如果當前方法調用了其他方法,方法返回之際,當前棧幀會傳回此方法的執行結果給前一個棧幀,接著,虛擬機會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀。
? ? Java方法有兩種返回函數的方式,一種是正常的函數返回,使用return指令;另一種是拋出異常。不管使用哪種方式,都會導致棧幀被彈出。
棧幀的內部結構
每個棧幀中存儲著
? ? 局部變量表(Local Variables)
? ? 操作數棧(Operand Stack)(或表達式棧)
? ? 動態鏈接(dynamic Linking)(或指向運行時常量池的方法引用)
? ? 方法返回地址(Return Address)(或方法正常退出或者異常退出的定義)
? ? 一些附加信息。
局部變量表(Local Variables)(簡而言之就是方法內部定義的形參、局部變量值、返回值)
? ? 局部變量表也被稱之為局部變量數組或本地變量表。
? ? 定義為一個數字數組,主要用于存儲方法參數和定義在方法體內的局部變量,這些數據類型包括各類基本數據類型、對象引用(reference),以及returnAddress類型。
? ? 由于局部變量表是建立在線程的棧上,是線程的私有數據,因此不存在數據安全問題。
? ? 局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數據項中。在方法運行期間是不會改變局部變量表的大小的。
? ? 方法嵌套調用的次數由棧的大小決定。一般來說,棧越大,方法嵌套調用次數越多。對一個函數而言,它的參數和局部變量越多,使得局部變量表膨脹,它的棧幀就越大,以滿足方法調用所需傳遞的信息增大的需求。進而函數調用就會占用更多的棧空間,導致其嵌套調用次數就會減少。
? ? 局部變量表中的變量只在當前方法調用中有效。在方法執行時,虛擬機通過使用局部變量表完成參數值到參數變量列表的傳遞過程。當方法調用結束后,隨著方法棧幀的銷毀,局部變量表也會隨之銷毀。
代碼示例
關于slot(槽)的理解
? ? 參數值的存放總是在局部變量數組的index0開始,到數組長度-1的索引結束。
? ? 局部變量表,最基本的存儲單元是Slot(變量槽)。
? ? 局部變量表中存放編譯期可知的各種基本數據類型(8種),引用類型(reference),returnAddress類型的變量。
? ? 在局部變量表里,32位以內的類型只占用一個slot(包括returnAddress類型),64位的類型(long和duble)占用兩個slot。(byte、shot、char在存儲前被轉換為int,boolean也被轉換為int,0表示false,非0表示true;long和double則占據兩個Slot)
? ? JVM會為局部變量表中的每一個Slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變量表中指定的局部變量。
? ? 當一個實例方法被調用的時候,它的方法參數和方法體內部定義的局部變量將會按照順序被復制到局部變量表中的每一個Slot上。
? ? 如果需要訪問局部變量表中一個64bit的局部變量值時,只需要使用前一個索引即可。(比如:訪問long或double類型變量)
? ? 如果當前幀是由構造方法或者實例方法創建的,那么該對象引用this將會存放在index為0的slot處(static方法中不允許使用this),其余的參數按照參數表順序繼續排列。
Slot的重復利用
? ? 棧幀中的局部變量表中的槽位是可以重用的,如果一個局部變量過了其作用域,那么在其作用域之后申明的新的局部變量就很有可能會復用過期局部變量的槽位,從而達到節省資源的目的。
? ? 此例子中變量c使用之前已經銷毀的變量b使用的slot的位置:
靜態變量與局部變量的對比
? ? 參數表分配完畢之后,再根據方法體內定義的變量的順序和作用域分配。
? ? 我們知道類變量表有兩次初始化的機會,第一次是在“準備階段”,執行系統初始化,對類變量設置零值,另一次則是在“初始化”階段,賦予程序員在代碼中定義的初始值。
? ? 和類變量初始化不同的是,局部變量表不存在系統初始化的過程,這意味著一旦定義了局部變量則必須認為的初始化,否則無法使用。
變量的分類
? ? 按照數據類型分:基本數據類型;引用數據類型。
? ? 按照在類中聲明的位置分:成員變量(類變量、實例變量);局部變量。
類變量在鏈接的初始化階段默認賦值,在初始化階段在靜態代碼塊賦值。
實例變量:隨著對象的創建,會在堆空間中分配實例變量空間,并進行默認賦值。
局部變量必須賦值才能用,否則編譯不通過。
補充說明
? ? 在棧幀中,與性能調優關系最為密切的部分就是前面提到的局部變量表。在方法執行時,虛擬機使用局部變量表完成方法的傳遞。
? ? 局部變量表中的變量也是重要的垃圾回收根節點,只要被局部變量表中直接或間接引用的對象都不會被回收。
操作數棧(用數組來實現的)
? ? 每一個獨立的棧幀中除了包含局部變量表以外,還包含一個后進先出(Last-In-First-Out)的操作數棧,也可以稱之為表達式棧(Expression Stack)。
? ? 操作數棧,在方法執行過程中,根據字節碼指令,往棧中寫入數據或提取數據,即入棧(push)/出棧(pop)。
?? ?? ? 某些字節碼指令將值壓入操作數棧,其余的字節碼指令將操作數取出棧。使用它們后再把結果壓入棧。比如:執行復制、交換、求和等操作。
? ? 如果被調用的方法帶有返回值的話,其返回值將會被壓入當前棧幀的操作數棧中,并更新PC寄存器中下一條需要執行的字節碼指令。
? ? 操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,這由編譯器在編譯期間進行驗證,同時在類加載過程中的類檢驗階段的數據流分析階段要再次驗證,
? ? 另外,我們說Java虛擬機的解釋引擎是基于棧的執行引擎,其中的棧指的就是操作數棧。
? ? 操作數棧,主要用于保存計算過程的中間結果,同時作為計算過程中變量臨時的存儲空間。
? ? 操作數棧就是JVM執行引擎的一個工作區,當一個方法剛開始執行的時候,一個新的棧幀也會隨之被創建出來,這個方法的操作數棧是空的。
? ? 每一個操作數棧都會擁有一個明確的棧深度用于存儲數值,其所需的最大深度在編譯期就定義好了,保存在方法的Code屬性中,為max_stack的值。
? ? 棧中的任何一個元素都是可以任意的Java數據類型。32bit的類型占用一個棧單位深度;64bit的類型占用兩個棧單位深度。
? ? 操作數棧并非采用訪問索引的方式來進行數據訪問的,而是只能通過標準的入棧(push)和出棧(pop)操作來完成一次數據訪問。
? ? 每一個獨立的棧幀中除了包含局部變量表意外,還包含一個后進先出的操作數棧,也可以稱之為表達式棧。
? ? 操作數棧,在方法執行過程中,根據字節碼指令,往棧中寫入數據或提取數據,即入某些字節碼指令將值壓入棧(push)/出棧(pop)。操作數棧,其余的字節碼指令將操作數取出棧,使用它們后再把結果壓入棧;比如:執行復制、交換、求和等操作。
? ? 如果被調用的方法帶有返回值的話,其返回值將會被壓入當前棧幀的操作數棧中,并更新PC寄存器中下一條需要執行的字節碼指令。
? ? 操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,這由編譯器在編譯期間進行驗證,同時在類加載過程中的類檢驗階段的數據流分析階段要再次驗證。
? ? 另外,我們說Java虛擬機的解釋引擎是基于棧的執行引擎,其中的棧指的就是操作數棧。
操作數棧+代碼解析
帶返回值的
?i++和++i的區別
棧頂緩存技術
? ? 前面提過,基于棧式架構的虛擬機所使用的零地址指令更加緊湊,但完成一項操作的時候必然需要使用更多的入棧和出棧指令,這同時也就意味著需要更多的指令分派(instruction dispatch)次數和內存讀/寫次數。
? ? 由于操作數是存儲在內存中的,因此頻繁地執行內存讀/寫操作必然會影響執行速度。為了解決這個問題,HotSpot JVM的設計者們提出了棧頂緩存(ToS,Top-of-Stack Cashing)技術,將棧頂元素全部緩存在物理CPU的寄存器中,以此降低對內存的讀/寫次數,提升執行引擎的執行效率。
? ? 寄存器:指令更少,執行速度更快。
總結
以上是生活随笔為你收集整理的你只知道JVM栈,知不知道栈帧、局部变量表、slot、操作数栈?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jvm运行时数据区是干啥的?CPU切换线
- 下一篇: 只知道java有反射可以说是动态语言,动