因爲跨平臺性的設計,JAVA的指令都是根據棧來設計的。
優勢:跨平臺,指令集小,編譯器容易實現。
缺點:性能降低,實現一樣的功能須要更多指令。java
棧是運行時的單位,堆是儲存的單位。數組
每一個線程在建立的時候都會建立一個虛擬機棧,其內部保存一個個的棧幀,對應着一次次的Java方法調用。
虛擬機棧主管Java程序的運行,它保存方法的局部變量,部分結果並參與方法的調用和返回。緩存
咱們可使用參數-Xss來設置虛擬機棧的大小。
IDEA能夠在run -> Edit Configurations進行設置。
安全
public class StackFrameTest {架構
public static void main(String[] args) { StackFrameTest test = new StackFrameTest(); test.methon1(); } private void methon1() { System.out.println("方法一開始執行"); methon2(); System.out.println("方法一執行結束"); } private void methon2() { System.out.println("方法二開始執行"); methon3(); System.out.println("方法二執行結束"); } private void methon3() { System.out.println("方法三開始執行"); System.out.println("方法三執行結束"); } }
每一個棧幀中存儲着如下內容:函數
局部變量表和操做數棧主要影響着棧幀的大小,棧幀的大小影響着虛擬機棧能存放多少棧幀。性能
局部變量表也被稱爲局部變量數組或本地變量表。測試
public class LocalVariablesTest {this
public static void main(String[] args) { LocalVariablesTest test = new LocalVariablesTest(); int i = 10; } }
javap反編譯後查看局部變量表,結果如圖:
其中,spa
除了javap指令,咱們也能夠經過JClasslib插件查看局部變量表。
局部變量表內容同上,start和length兩個屬性決定變量的做用域,變量在start標誌的下一行開始生效。
類方法爲何不能使用this關鍵字?
由於在類方法的局部變量表中不存在this變量。
棧幀中的局部變量表中的slot是能夠重用的,若是一個局部變量過了其做用域,那麼在其做用域後申請的新局部變量就頗有可能複用這個過時變量的槽位。測試代碼:
public void test() { int a = 1; { int b = 0; b = a + 1; } int c = a + 1; }
測試代碼的局部變量圖以下:
經過索引,咱們能夠看出c複用了b的slot。
變量的分類:
根據數據類型分類:
根據在類中聲明的位置分類:
成員變量
public void testadd() { byte i = 15; int j = 8; int k = i + j; }
反編譯後的字節碼指令以下:
0 bipush 15 2 istore_1 3 bipush 8 5 istore_2 6 iload_1 7 iload_2 8 iadd 9 istore_3 10 return
字節碼指令解析:
0:將數值15壓入操做數棧。
2: 將操做數棧的當前棧幀壓入局部變量表,索引爲1。
3:將數值8壓入操做數棧。
5:將操做數棧的當前棧幀壓入局部變量表,索引爲2。
6:讀取局部變量表中索引爲1的變量進操做數棧。
7:讀取局部變量表中索引爲2的變量進操做數棧。
8:執行加操做,並將結果壓入操做數棧。
9:將操做數棧的當前棧幀壓入局部變量表,索引爲3。
10:方法正常返回結束。
有些地方可能會將方法返回地址,動態連接和附加信息稱爲幀數據區。
public class DynamicLinkingTest { int num = 1; public void methonA() { System.out.println("methonA"); } public void methonB() { System.out.println("methonB"); methonA(); num++; } }
反編譯以後的執行指令以下:
Code: stack=3, locals=1, args_size=1 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String methonB 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: aload_0 9: invokevirtual #7 // Method methonA:()V 12: aload_0 13: dup 14: getfield #2 // Field num:I 17: iconst_1 18: iadd 19: putfield #2 // Field num:I 22: return
咱們能夠看到在指令後出現大量的#開頭的符號引用,這些引用能夠在編譯後的常量池中找到對應的實際方法,測試代碼編譯後的常量池以下圖:
爲了提供一些符號和常量,便於指令的識別,減小內存,提升複用。
在JVM中,將符號引用轉換爲調用方法的直接引用與方法的綁定機制相關。
根據兩種連接對應的綁定機制,綁定是指一個字段,方法或者類在符號引用被替換成直接引用的過程,這僅僅發生一次。
非虛方法:若是方法在編譯期就肯定了具體的調用版本,這個版本在運行時是不可變的,這樣的方法稱爲非虛方法。如靜態方法,私有方法,final方法,實例構造器,父類方法都是非虛方法。其餘方法都稱爲虛方法。
虛擬機中提供了一下幾條方法調用指令:
普通調用指令
動態調用指令
前四條指令方法的調用執行不可人爲干預,而動態調用支持由用戶肯定方法版本。其中invokestatic和invokespecial指令調用的方法稱爲非虛方法,其他的(final修飾的除外)稱爲虛方法。
沒有顯式使用super的方法調用也會被認爲是虛方法,使用invokevirtual調用。
JDK7中爲了實現動態類型語言支持而作出的改進,出現了invokedynamic指令。但直到JDK8的Lambda表達式的出現,invokedynamic指令的生成,在JAVA種纔有了直接的生成方式。
這兩種語言最重要的區別在於對類型的檢查是在編譯期間仍是在運行期間,前者爲靜態類型語言,後者爲動態類型語言。直白的說,靜態語言判斷變量的類型,動態語言判斷變量值的類型。
方法重寫的本質
IllegalAccessError異常介紹:
程序試圖訪問或修改一個屬性或調用一個方法,這個屬性或方法你沒有權限訪問。通常來講這個會引發編譯器異常,若是發生在運行期間,就說明一個類發生了不兼容的改變。
爲了提升性能,JVM在方法區創建一個虛方法表來實現,使用索引表來代替查找。
虛方法表主要在類加載的連接解析階段建立。
存放調用該方法的程序計數器的值。在方法結束以後都須要返回到該方法被調用的位置,方法正常退出時,調用者的程序計數器的值做爲返回地址,即調用該方法指令的下一條指令的地址。而經過異常退出須要經過異常表肯定,棧幀中不保存這部分信息。
因此,經過異常完成的方法退出不會給它的上層調用者任何的返回值。
補充:方法返回指令!
這個部分可能會有,也可能沒有,主要看虛擬機的具體實現。