棧幀中都有啥東西
我的所有文章同步更新與Github-- Java-Notes ,想了解JVM,HashMap原始碼分析,spring相關,劍指offer題解(Java版),可以點個star。可以看我的github主頁,每天都在更新喲。
邀請您跟我一同完成 repo
棧幀是虛擬機器棧的一個單位,之前講解了執行時資料區和類載入的過程,現在我們看下虛擬機器中棧幀都是啥樣子的,這個應該算是執行時資料區(JVM記憶體結構的補充),如果不瞭解可以參考我的這篇博文 JVM記憶體結構
執行時棧幀結構
執行時棧幀中儲存了以下內容
- 區域性變數
- 運算元棧
- 動態連結
- 返回地址
- 附加資訊
- ….

每一個方法的呼叫開始和結束都是棧的壓入(入棧)和彈出(出棧)的過程
區域性變量表
是什麼
區域性變量表是 一組變數值儲存空間 ,用於存放 方法引數 和 方法內部 定義的 區域性變數 。
大小是編譯的時候寫進了位元組碼裡面的,在Code屬性中的max_local屬性,即下面的local

有什麼
區域性變量表的容量以 變數槽 (Variable Slot)為 最小單位 ,虛擬機器中並沒有明確指明一個Slot應占用的記憶體空間大小,只是很有導向性的說到每個Slot都 應該能存放 一個下面8種類型的其中一個。(如果是long或者double這種64位的資料型別,則需要兩個Slot)
- boolean
- byte
- char
- short
- int
- float
- reference
- returnAddress
前六種應該不用說,是基本的資料型別,reference是啥呢
reference
reference是一個物件例項的引用
作用:
- 從此引用中 直接或間接 地 查詢 到 物件在Java堆 中的資料存放的 起始地址索引
- 從此引用中 直接或間接 地 查詢 到物件所屬資料型別在 方法區 中的 儲存的型別物件 (因為類資訊在方法區中儲存)
returnAddress
為位元組碼指令jsr、jsr_w和ret提供的,指向了一條位元組碼指令的地址
已經很少見了。
注意
區域性變量表中的 區域性變數 和之前將類載入的時候的 類變數 (static修飾)不一樣,他沒有所謂的"準備階段",所以沒有設定初始值的階段。不知道的可以參考我類載入這篇文章,看了準備階段,應該就知道了。 類載入過程
所以我們在寫程式的時候這樣寫,對比你就知道了

其他型別零值

Slot複用
不使用的物件,應當手動賦值為null
為了儘可能節省棧空間,區域性變量表的 Slot可以複用 。方法體中定義的變數, 其作用域並不一定覆蓋整個方法體 ,如果當前位元組碼PC計數器的值已經超出了某個變數的作用域,那這個變數對應的Slot就可以交給其他變數使用。
不過這樣的做法,會有一些缺點,我們來看下面的程式碼示例
public class Test2 { public static void main(String[] args) { byte[] placeholder = new byte[64 * 1024 * 1024]; System.gc(); } } 複製程式碼
我們通過配置虛擬機器引數 -verbose:gc
來列印垃圾回收的結果

我們看到他並沒有回收。
我們修改一下程式碼
public class Test2 { public static void main(String[] args) { { byte[] placeholder = new byte[64 * 1024 * 1024]; } System.gc(); } } 複製程式碼

他還是沒有進行回收,按理說 placeHolder 的作用域只在花括號中,在執行gc方法的時候,他就已經不可能用了,算是已經"死"了的物件了,為什麼沒有回收呢?
我們再修改一下
public class Test2 { public static void main(String[] args) { { byte[] placeholder = new byte[64 * 1024 * 1024]; } inta = 0; System.gc(); } } 複製程式碼

我們看到,這次垃圾回收器工作了,為什麼呢?
placeholder 能否被回收的根本原因是: 區域性變量表的Slot是否還存有關於placeholder陣列物件的引用。
第一次修改中,程式碼雖然離開了該變數的作用域,但是在此之後, 沒有任何對區域性變量表的讀寫操作 ,該變數 原本佔用的Slot 還沒有被任何其他變數 複用 ,所以作為GC Root 一部分的區域性變量表仍然保持著對他的關聯(不瞭解什麼可以作為GC Root的話,可以參考我的這篇文章 JVM垃圾回收 )
而第二次,則改變了上面的這種情況
所以當遇到一個方法,其後面的程式碼有一些耗時很長的操作,而前面又定義了佔用大量記憶體、實際上已經用不到的變數,應當手動設定其為null。
很多工具類都有這個操作,比如 ArrayList和Stack中的remove方法,你也可以找下其他的工具類中的方法,看是否有此類操作


運算元棧
運算元棧記錄了一個方法執行過程中的位元組碼指令,他往運算元棧中進行入棧和出棧
大小在編譯的時候也已經確定了,位元組碼檔案中的Code屬性中的max_stacks資料項,即下圖的 stack

當一個方法剛執行的時候,運算元棧是空的,在方法執行的過程中,會有各種位元組碼指令往運算元棧中入棧和出棧。
動態連線
每一個棧幀都包含一個 指向執行時常量池 中該棧幀所屬的 方法的引用 ,持有這個引用是為了支援方法呼叫過程中的 動態連線
如果你看了 位元組碼檔案構成 和 類載入過程 ,你應該知道,位元組碼檔案中有很多符號引用。
這些符號引用一部分會在 類載入的解析階段 或者 第一次使用的時候 轉化為直接引用,這種轉化稱為 靜態解析
另一部分會在 執行期間 轉化為直接引用,這部分稱為 動態連線
動態連線會在這篇文章 方法呼叫 中講解