0032-虛擬機棧

1 簡介

java虛擬機棧,早期也稱爲java棧,每個線程在創建時,都會創建一個虛擬機棧,其內部包含一個個棧幀(Stack Frame),對應一次次的方法調用,虛擬機棧是線程私有的,沒有GC,有可能會出現StackOverFlowError或OutOfMemoryError

2 棧配置

# 默認是1m,-XX:+PrintFlagsFinal可以打印最終配置,-XX:+PrintFlagsInitial可以打印初始默認配置
-Xss2m

棧的大小會決定方法調用的深度,也即能存儲多少個棧幀

3 棧幀

棧幀內容

1. 局部變量表(Local Variables)

2. 操作數棧(Operand Stack)

3. 動態鏈接(Dynamic Linking)(指向運行時常量池的方法引用)

4. 方法返回地址(Return)

5. 一些附加信息

3.1 局部變量表(local variables)

簡介

1. 局部變量表是一個數字數組,主要用於存儲方法參數
和定義在方法體內的局部變量,包括基本數據類型,對象引用,以及returnAddress類型

2. 局部變量建立在線程的棧上,是線程的私有數據,不存在線程安全問題

3. 局部變量表所需的容量大小是在編譯期確定下來的,保存在方法的code屬性的maximum local variables數據項中,
運行時不會改變局部變量表的大小

4. 棧幀的的大小受局部變量表影響,局部變量越多,棧幀越大,方法可以嵌套的次數越少

5. 局部變量數組,最基本的單位是Slot(變量槽)

6. 在局部變量表裏32位以內的類型只佔用一個slot(包括returnAddress類型)
64位的類型(long和double)佔用兩個slot
(byte,short,char在存儲前被轉換爲int,boolean也被轉換爲int,0爲false,非0表示true)

7. 局部變量表爲每一個slot分配一個訪問索引,通過這個索引可以訪問局部變量值
    7.1 如果需要訪問64位的局部變量值時,只需要使用前一個索引即可
    7.2 如果當前棧幀是由構造方法或者實例方法創建,該對象應用this會放在index爲0的slot處

8. 棧幀中的局部變量表中的槽位可以重用,如果一個局部變量過了作用域,後面聲明的局部變量可以覆蓋這個slot

靜態變量與局部變量的對比

1. 靜態變量有兩次初始化的機會,一次在“準備階段”的零值初始化,另一次在“初始化階段”實際賦值

2. 局部變量沒有零值初始化的過程,所以創建必須賦值,否則無法使用

3.2 操作數棧(Operand Stack)

簡述

1. 操作數棧,主要用於保存計算過程中的中間結果,同時作爲計算過程中變量零時的存儲空間

2. 操作數棧是jvm執行引擎的一個工作區,當一個方法剛開始執行的時候,一個新的棧幀也會隨之被創建出來,這個方法的操作數棧是空的。

3. 每個操作數棧都會擁有一個明確的棧深度用於存儲數值,其所需的最大深度在編譯期就定義好了
保存在方法Code屬性中,爲max_stack的值

4. 棧中的任何一個元素都是可以任意的java數據類型
    4.1 32bit的類型佔用一個棧單位深度
    4.2 64bit的類型佔用兩個棧單位深度

5. 如果被調用的方法帶有返回值的話,其返回值將會被壓入當前棧幀的操作數棧中,並更新pc寄存器中下一條需要執行的字節碼指令

6. java虛擬機的解釋引擎是基於棧的執行引擎,其中的棧指的就是操作數棧

代碼追蹤


在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

3.3 動態鏈接(Dynamic Linking)

簡述

1. 每一個棧幀內部都包含一個指向運行時常量池中該棧幀所屬方法的引用。
包含這個引用的目的就是爲了支持當前方法的代碼能夠實現動態鏈接(Dynamic Linking)

2. 在java源文件被編譯到字節碼文件中時,所有的變量和方法引用都作爲符號引用,保存在class文件的常量池裏。
比如:描述一個方法調用了另外的其它方法時,就是通過常量池中指向方法的符號引用來表示的,那麼動態鏈接的作用就是爲了將這些符號引用轉換爲調用方法的直接引用

3. 這些符號引用一部分會在類加載階段或者第一次使用的時候就轉化爲直接引用,這種轉化稱爲靜態解析。 另外一部分將在每一次運行期間轉化爲直接引用,這部分稱爲動態連接。

在這裏插入圖片描述

方法調用指令
在class字節碼碼中,不同的方法有不同的指令

  • 普通調用指令
1. invokestatic:調用靜態方法,解析階段確定唯一方法版本

2. invokespecial:調用<init>方法,私有及父類方法,解析階段確定唯一方法版本

3. invokevirtual:調用所有虛方法

4. invokeinterface:調用接口方法
  • 動態調用指令
5. invokedynamic:動態解析出需要調用的方法,然後執行

由invokestatic指令和invokespecial指令調用的方法稱爲非虛方法(便宜期就確定了具體的調用版本,運行時不可變的方法),其餘的(final修飾的除外),稱爲虛方法

方法重寫的本質

1. 找到操作數棧頂的第一個元素所執行的對象的實際類型,記作C

2. 如果在類型C中找到與常量中的描述符和名稱都相符的方法,則進行訪問權限校驗,
如果通過則返回這個方法的直接引用,查找過程結束;如果不通過,則返回java.lang.IllegalAccessError異常

3. 否則,按照繼承關係從下往上依次對C的各個父類進行第2步的搜索和驗證過程

4. 如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常

虛方法表

1. 在面向對象的編程中,會很頻繁的使用動態分派,如果每次動態分派都按上述步驟搜索,會影響執行效率
爲了提高性能,jvm採用在類的方法區建立一個虛方法表來實現,使用索引表來代替查找

2. 每個類都有一個虛方法表,存在各個方法的實際入口

3. 虛方法表在類加載的鏈接階段被創建並開始初始化

3.4 方法返回地址(Return Address)

簡述

1. 一個方法的結束,有兩種方式:
    1.1 正常執行完成
    1.2 出現未處理的異常,非正常退出

2. 方法正常退出時,調用者的pc計數器的值作爲返回地址,即調用該方法指令的下一條指令地址;
通過異常退出的,需要根據異常表確定下一條指令地址(字節碼指令中可以看到,異常表)

3.5 一些附加信息

4. 棧的相關面試題

  1. 舉例棧溢出的情況?(StackOverFlowError)

通過-Xss設置棧的大小

  1. 調整棧的大小就能保證棧不出現溢出嗎?

不能

  1. 垃圾回收是否會涉及到虛擬機棧?

不會

  1. 方法中定義的局部變量是否線程安全?

沒有發生逃逸是線程安全的,否則就不安全

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章