認識局部變量表
- 局部變量表:Local Variables,被稱之為局部變量數組或本地變量表。
- 定義為一個數字數組,主要用于存儲方法參數和定義在方法體內的局部變量,這些數據類型包括各類基本數據類型、對象引用(reference),以及returnAddress類型。
- 八大基本數據類型都可以轉換為數字。
- 由于局部變量表是建立在線程的棧上(棧幀內),是線程的私有數據,因此不存在數據安全問題。
- 局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數據項中。在方法運行期間是不會改變局部變量表的大小的。
- 方法嵌套調用的次數由棧的大小決定。一般來說,方法嵌套調用次數越多,棧越大。
- 對一個函數而言,它的參數和局部變量越多,使得局部變量表膨脹,它的棧幀就越大,以滿足方法調用所需傳遞的信息增大的需求。
- 進而函數調用就會占用更多的棧空間,導致其嵌套調用次數就會減少。
- 局部變量表中的變量只在當前方法調用中有效。
- 在方法執行時,虛擬機通過使用局部變量表完成參數值到參數變量列表的傳遞過程。
- 當方法調用結束后,隨著方法棧幀的銷毀,局部變量表也會隨之銷毀。
總結
- 棧是由多個棧幀(方法)組成。
- 若固定棧的大小,則最上層棧幀超出限制后會拋出StackoverflowError異常。
- 覺得棧幀大小最主要部分是局部變量表。
- 局部變量表大小編譯時就已確認,比如方法內定義十個對象,大小為10,而不是十個對象的內存大小。(相對來講10)
問題:局部變量表大小編譯時確定,為什么編譯時不能確定是否會棧溢出呢?
- 方法調用伴隨著出棧入棧,編譯期不會確定棧內存在多少棧幀。
- 只是確認了棧幀內的局部變量表大小。不是整個棧內容都確定。
關于 Slot (變量槽)的理解
- 參數值的存放總是從局部變量數組索引 0 的位置開始,到數組長度-1的索引結束。
- 局部變量表,最基本的存儲單元是Slot(變量槽),局部變量表中存放編譯期可知的各種基本數據類型(8種),引用類型(reference),returnAddress類型的變量。
- 在局部變量表里,32位以內的類型只占用一個slot(包括returnAddress類型),64位的類型占用兩個slot(long和double)。
- JVM會為局部變量表中的每一個Slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變量表中指定的局部變量值。
- 當一個實例方法被調用的時候,它的方法參數和方法體內部定義的局部變量將會按照順序被復制到局部變量表中的每一個slot上。
- 如果需要訪問局部變量表中一個64bit的局部變量值時,只需要使用前一個索引即可。(比如:訪問long或double類型變量)
- 如果當前幀是由構造方法或者實例方法創建的,那么該對象引用this將會存放在index為0的slot處,其余的參數按照參數表順序繼續排列。
總結
- long和double占兩個槽,其他占一個。
- 如果訪問的是非實例方法(即類的方法)那么第0位表示的就是this。static方法無this。
- 分配順序:this變量->方法中的其他形參->方法內部定義的局部變量。
代碼實操
image.png
[圖片上傳中...(image.png-317529-1606998517442-0)]
- Start與Length組成變量作用域。
- Slot為局部變量所占槽位,若圖中num替換為double或long類型,則num的下一個槽位將從4開始。
非static方法存在this局部變量,static方法則不存在
image.png
Solt的重復利用
棧幀中的局部變量表中的槽位是可以重用的,如果一個局部變量過了其作用域,那么在其作用域之后申明新的局部變量變就很有可能會復用過期局部變量的槽位,從而達到節省資源的目的。
public void test4() {
int a = 0;
{ int b = 0; b = a + 1;
}
//變量c使用之前已經銷毀的變量b占據的slot的位置
int c = a + 1;
}
局部變量 c 重用了局部變量 b 的 slot 位置
image.png
補充說明
- 類變量表有兩次初始化的機會,第一次是在鏈接中的“準備階段”,執行系統初始化,對類變量設置零值(final修飾的static不會),另一次則是在“初始化”階段,賦予程序員在代碼中定義的初始值。
- 和類變量初始化不同的是,局部變量表不存在系統初始化的過程,這意味著一旦定義了局部變量則必須人為的初始化,否則無法使用(編譯不會通過)。
- 在棧幀中,與性能調優關系最為密切的部分就是局部變量表。在方法執行時,虛擬機使用局部變量表完成方法的傳遞。
- 局部變量表中的變量也是重要的垃圾回收根節點,只要被局部變量表中直接或間接引用的對象都不會被回收。
個人補充:
學到此處,一直迷惑與不同類間方法調用流程,出入棧的順序,具體誰來掌控,想了一天,有些思路,以下僅代表個人粗略觀點,謹慎借鑒。
- 設想目前有兩個類,One類里面包含一個mian方法,Two類里面包含一個Add求和方法。main方法需要傳參調用Add方法。
- 單個文件編譯,先One則報錯,因為需要調用的Add方法的類未進行編譯。一般都是直接編譯整個項目,所以不會出現這個問題。
- Error:(16, 9) java: 找不到符號 符號: 類 JvmBeginTest 位置: 類 fangk.jvm.CshTest
- 兩文件編譯完成之后,每個類的字節碼文件中,方法(棧幀)的局部變量表大小確定(根據參數和內部定義變量),只是大小不是內容。
- 同樣確認的還有操作數棧深度、字節碼長度(指令集)。
- 確認的只是些代碼編譯能確認的。
- 運行main方法,相當于啟動一個線程,PC寄存器先記錄main方法的第一步,假設共10步,在第五步時調用Add方法。
- 前五步執行時在第一個棧幀內,棧幀內的局部變量表或其他會等待調用的Add棧幀執行完,繼續執行第六步。
- PC寄存器來記錄下一步的指令,調用方法時,會把被調用的方法入到棧頂,執行完出棧。繼續執行調用方法的棧幀。
- 說的不明白,理解的不太透徹,改天專門寫個博客記錄。