棧幀中都有啥東西

大綱

前言


個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。java

邀請您跟我一同完成 repogit


棧幀是虛擬機棧的一個單位,以前講解了運行時數據區和類加載的過程,如今咱們看下虛擬機中棧幀都是啥樣子的,這個應該算是運行時數據區(JVM內存結構的補充),若是不瞭解能夠參考個人這篇博文 JVM內存結構github

運行時棧幀結構

運行時棧幀中存儲瞭如下內容spring

  • 局部變量
  • 操做數棧
  • 動態連接
  • 返回地址
  • 附加信息
  • ….

每個方法的調用開始和結束都是棧的壓入(入棧)和彈出(出棧)的過程數組

局部變量表

是什麼

局部變量表是一組變量值存儲空間,用於存放方法參數方法內部定義的局部變量工具

大小是編譯的時候寫進了字節碼裏面的,在Code屬性中的max_local屬性,即下面的local源碼分析

有什麼

局部變量表的容量以變量槽(Variable Slot)爲最小單位,虛擬機中並無明確指明一個Slot應占用的內存空間大小,只是頗有導向性的說到每一個Slot都應該能存放一個下面8種類型的其中一個。(若是是long或者double這種64位的數據類型,則須要兩個Slot)spa

  • boolean
  • byte
  • char
  • short
  • int
  • float
  • reference
  • returnAddress

前六種應該不用說,是基本的數據類型,reference是啥呢code

reference

reference是一個對象實例的引用cdn

做用:

  • 今後引用中直接或間接查找對象在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];


        }
      	int  a = 0;
        System.gc();
    }
}
複製代碼

咱們看到,此次垃圾回收器工做了,爲何呢?

placeholder 可否被回收的根本緣由是:局部變量表的Slot是否還存有關於placeholder數組對象的引用。

第一次修改中,代碼雖然離開了該變量的做用域,可是在此以後,沒有任何對局部變量表的讀寫操做,該變量本來佔用的Slot尚未被任何其餘變量複用,因此做爲GC Root 一部分的局部變量表仍然保持着對他的關聯(不瞭解什麼能夠做爲GC Root的話,能夠參考個人這篇文章 JVM垃圾回收)

而第二次,則改變了上面的這種狀況

因此當遇到一個方法,其後面的代碼有一些耗時很長的操做,而前面又定義了佔用大量內存、實際上已經用不到的變量,應當手動設置其爲null。

不少工具類都有這個操做,好比 ArrayList和Stack中的remove方法,你也能夠找下其餘的工具類中的方法,看是否有此類操做

操做數棧

操做數棧記錄了一個方法執行過程當中的字節碼指令,他往操做數棧中進行入棧和出棧

大小在編譯的時候也已經肯定了,字節碼文件中的Code屬性中的max_stacks數據項,即下圖的stack

當一個方法剛執行的時候,操做數棧是空的,在方法執行的過程當中,會有各類字節碼指令往操做數棧中入棧和出棧。

動態鏈接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬的方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接

若是你看了字節碼文件構成類加載過程,你應該知道,字節碼文件中有不少符號引用。

這些符號引用一部分會在類加載的解析階段或者第一次使用的時候轉化爲直接引用,這種轉化稱爲靜態解析

另外一部分會在運行期間轉化爲直接引用,這部分稱爲動態鏈接

動態鏈接會在這篇文章 方法調用 中講解

返回地址

一個方法執行後,只有兩種方法能夠退出:

  • return,正常退出
  • 異常,而且不在該方法中處理

方法退出時可能的操做:

  • 恢復上層方法的局部變量表和操做數棧
  • 把返回值(若是有的話)壓入調用者戰爭的操做數棧中
  • 恢復PC計數器的值,以指向方法調用指令後面的一條指令

附加信息

這部分信息徹底取決於具體的虛擬機實現,有的可能有,有的沒有。

總結

  • 棧幀中主要有4種信息
    • 局部變量表
      • 單位Slot
      • Slot複寫
    • 操做數棧
      • 方法執行時,入棧出棧各類字節碼指令
    • 動態鏈接
      • 運行期間,將符號引用轉化爲直接引用
    • 返回地址
      • return
      • 異常而且沒有在該方法中處理
    • ….
相關文章
相關標籤/搜索