操作數棧
-
每一個獨立的棧楨中除了包含局部變量表以外,還包含一個后進先出的操作數棧,也可以稱之為表達式棧。
-
操作數棧,在方法執行過程中,根據字節碼指令,往棧中寫入數據或提取數據,即入棧/出棧
- 某些字節碼指令將值壓入操作數棧,其余的字節碼指令將操作數取出棧。使用他們后再把結果壓入棧。
- 比如:執行復制、交換、求和等操作。
-
如果被調用的方法帶有返回值的話,其返回值將會被壓入當前棧楨的操作數棧中,並更新PC寄存器中下一條需要執行的字節碼指令。
-
操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,這由編譯器在編譯器期間進行驗證,同時在類加載過程中的類檢驗階段的數據流分析階段再次驗證。
-
另外,我們說Java虛擬機的解釋引擎是基於棧的執行引擎,其中的棧指的就是操作數棧。
-
操作數棧,主要用於保存計算過程的中間結果,同時作為計算過程中變量臨時存儲空間。
-
操作數棧就是JVM執行引擎的一個工作區,當一個方法剛開始執行的時候,一個新的棧楨也會隨之被創建出來,這個方法的操作數棧是空的。
-
每一個操作數棧都會擁有一個明確的棧深度用於存儲數值,其所需的最大深度在編譯器就定義好了,保存在方法的Code屬性中,為max_stack的值。
-
棧中的任何一個元素都是可以任意的Java數據類型。
- 32bit的類型占用一個棧單位深度。
- 64bit的類型占用兩個棧單位深度。
-
操作數棧並非采用訪問索引的方式來進行數據訪問的,而是只能通過標准的入棧和出棧操作來完成一次數據訪問。
動態鏈接(指向運行時常量池的方法引用)
- 每一個棧楨內部都包含一個指向運行時常量池中該棧楨所屬方法的引用。包含這個引用的目的就是為了支持當前方法的代碼能夠實現動態鏈接。比如: invokedynamic指令
- 在Java源文件被編譯到字節碼文件中時,所有的變量和方法引用都作為符號引用保存在class文件的常量池里。比如:描述一個方法調用了另外的其他方法時,就是通過常量池中指向方法的符號引用來表示的,那么動態鏈接的作用就是為了將這些符號引用轉換為調用方法的直接引用。
那么什么是動態鏈接,具體看下面的例子
public class DynamicTest {
int num = 0;
private void methodA(){
System.out.println("methodA");
}
private int methodB(){
methodA();
num++;
return num;
}
}
字節碼指令 javap 反編譯后
常量池在運行時會放入方法區,所以叫作運行時常量池。
常量池的作用是:提供一些符號和常量,便於指令的識別。
方法的調用
在JVM中,將符號引用轉換為調用方法的直接引用於方法的綁定機制相關。
- 靜態鏈接:當一個字節碼文件被裝載進JVM內部時,如果被調用的目標方法在編譯期可知,且運行期保持不變時。這種情況下將調用方法的符號引用轉換為直接引用的過程稱之為靜態鏈接。
- 動態鏈接:如果被調用的方法在編譯期無法被確定下來,也就是說,只能夠在程序運行期將調用方法的符號引用轉換為直接引用,由於這種引用轉換過程具備動態性,因此也就被稱之為動態鏈接。
對應的方法的綁定機制為:早期綁定和晚期綁定。綁定是一個字段、方法或者類在符號引用被替換為直接引用的過程,這僅僅發生一次。 - 早期綁定:就是指被調用的目標方法如果在編譯期可知,且運行期保持不變時,即可將這個方法與所屬的類型進行綁定,這樣一來,由於明確了被調用的目標方法究竟是哪一個,因此也就可以使用靜態鏈接的方式將符號引用轉換為直接引用。
- 晚期綁定:如果被調用的方法在編譯期無法被確定下來,只能夠在程序運行期根據實際的類型綁定相關的方法,這種綁定方式也就被稱之為晚期綁定。
非虛方法(編譯期確定具體調用方法)
靜態方法、私有方法、final方法、實例構造器、父類方法都是非虛方法。
其他方法都是稱為虛方法。虛擬機提供了一下幾條方法調用指令:
普通調用指令:
1.invokestatic
:調用靜態方法,解析階段確定唯一方法版本
2.invokespecial
:調用方法、私有及父類方法,解析階段確定唯一方法版本
3.invokevirtual
:調用所有虛方法(final方法除外)
4.invokeinterface
:調用接口方法動態調用指令
5.invokedynamic:動態解析出需要調用的方法,然后執行
前四條指令固化在虛擬機內部,方法的調用執行不可人為干預,而invokedynamic指令則支持由用戶確定方法版本。其中invokestatic指令和invokespecial指令調用的方法稱為非虛方法,其余的(final修飾的除外)稱為虛方法。
方法調用:方法重寫的本質
Java語言中方法重寫的本質
- 1.找到操作數棧楨的第一個元素所執行的對象的實際類型,記作C。
- 2.如果在過程結束;如果不通類型C中找到與常量中的描述符合簡單名稱都相符的方法,則進行訪問權限校驗,如果通過則訪問這個方法的直接引用,查找不通過,則返回java.lang.IllegalAccessError異常。
- 3.否則,按照繼承關系從下往上依次對C的各個父類進行第2步的搜索和驗證過程。
- 4.如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。
IllegalAccessError介紹
程序試圖訪問或修改一個屬性或調用一個方法,這個屬性或方法,你沒有權限訪問。一般的這個會引起編譯期異常。這個錯誤如果發生在運行時,就說明一個類發生了不兼容的改變。
方法調用:虛方法表
- 在面向對象的編程中,會很頻繁的使用動態分派,如果在每次動態分派的過程中都要重新再類的方法元數據中搜索合適的目標的話就可能影響到執行效率。因此,為了提高性能,JVM采用在類的方法區建立一個虛方法表(非虛方法不會出現在這個表中)來實現。使用索引表來代替查找。
- 每個類中都有一個虛方法表,表中存放着各個方法的實際入口。
- 那么虛方法表什么時候被創建?
虛方法表會在類加載的鏈接階段被創建並開始初始化,類的變量初始值准備完成之后,JVM會把該類的方發表也初始化完畢。
方法返回地址
- 存放調用該方法的PC寄存器的值
- 一個方法的結束,有兩種方式:
- 正常執行完成
- 出現未處理的異常,非正常退出
- 無論通過哪種方式退出,在方法退出后都返回到該方法被調用的位置。方法正常退出時,調用者的PC計數器的值作為返回地址,即調用該方法的指令的下一個指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧楨中一般不會保存這部分信息。
當一個方法喀什執行后,只有兩種方式可以退出這個方法:
1.執行引擎遇到任意一個方法返回的字節碼指令,會有返回值傳遞給上層的方法調用者,簡稱正常完成出口; - 一個方法的正常調用完成之后究竟需要使用哪一個返回指令還需要根據方法返回值的實際數據類型而定。
- 在字節碼指令匯總,返回指令包含ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn以及areturn,另外還有一個return指令供聲明為void的方法、實例初始化方法、類和接口的初始化方法使用。
2.在方法執行的過程中遇到了異常,並且這個異常沒有在方法內進行處理,也就是只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出。簡稱異常完成出口。
方法執行過程中拋出異常時的異常處理,存儲在一個異常處理表,方便發生異常的時候找到處理異常的代碼。
字節碼異常表
Exception | tbale | ||
---|---|---|---|
from | to | target | type |
4 | 16 | 19 | any |
19 | 21 | 19 | any |
上面的數字from或to以及target的數字代表的是字節碼地址,type是異常類型。
棧面試的題目
- 舉例棧溢出的情況?(StackOverflowError)
通過設置-Xss設置棧的大小 - 調整棧大小就能保證不出現溢出嗎?
不能 - 分配的棧內存越大越好嗎?
不是 - 垃圾回收是否會涉及到虛擬機棧?
不會