本文參考自來自周志明《深刻理解Java虛擬機(第2版)》,拓展內容建議讀者能夠閱讀下這本書。
文字版以下:java
一個存儲單位稱爲一個Slot(32位)緩存
爲了讓全部數據類型的局部變量都可以存儲到局部變量表中而設定了定長的Slot長度ide
局部變量第0位Slot優化
Slot重用:離開了做用域的局部變量佔用的Slot能夠被重用this
對垃圾回收的影響:離開了做用域的局部變量所在的Slot只有在真的不存在對原有局部變量的引用時,GC纔會回收相應的對象實例spa
局部變量和類字段的區別code
生命週期不一樣對象
賦初值不一樣繼承
reference類型索引
一個存儲單位稱爲一個棧容量
爲了讓全部數據類型的局部變量都可以存儲到棧中而設定了定長的棧容量
爲了知足方法指令的運行時解析
退出方法的方式
退出方法執行的操做
非虛方法:在類加載的時候就能夠將符號引用解析爲直接引用的方法,即不存在因覆蓋而產生多版本的方法
虛方法:在類加載的時候有可能沒法肯定符號引用能夠解析爲哪一個直接引用的方法
invokestatic
invokespecial
invokevirtual
invokeinterface
invokedynamic
靜態分派 Method Overload Resolution
典型應用是方法重載 Overload
invokevirtual 選擇的方法版本的符號引用
動態分派
典型應用是方法重寫 Override
invokevirtual 選擇的方法版本的符號引用
類B的方法m(P)重寫了B所繼承的類A的方法m(P),實際類型爲B,A b = new B(),即靜態類型爲A的對象b,發生了方法調用b.m(p)
invokevirtual A.m
運行期
在B中尋找與invokevirtual調用的方法符號引用名稱和簽名一致的方法
找到了的話說明實際類型B中就有了這個方法m(P),而後斷定該方法是否知足訪問權限
沒找到
在B的父類中自下向上尋找與invokevirtual調用的方法符號引用名稱和簽名一致的方法
找到了的話說明實際類型B的父類中有這個方法m(P),而後斷定該方法是否知足訪問權限
單分派:根據單個方法宗量進行方法選擇
多分派:根據多個方法宗量進行方法選擇
穩定優化手段:虛方法表 提早記錄避免運行時搜索的手段
非穩定的激進優化手段
源碼定義了繼承了Human
的Man
和Woman
,關鍵在於看一下編譯器將test.sayHi(human);
、test.sayHi(woman);
、test.sayHi(man);
編譯成了什麼,也就是說靜態階段編譯器重載方法的方法分派是什麼樣的。
class Human{ } class Man extends Human{ } class Woman extends Human{ } public class DispatchTest { public void sayHi(Human human){ System.out.println("Hi human"); } public void sayHi(Man man){ System.out.println("Hi man"); } public void sayHi(Woman woman){ System.out.println("Hi woman"); } public static void main(String[] args) { Human human = new Human(); Human man = new Man(); Human woman = new Woman(); DispatchTest test = new DispatchTest(); test.sayHi(human); test.sayHi(woman); test.sayHi(man); } }
javap獲取到的class字節碼解釋:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=5, args_size=1 0: new #7 // class Human 3: dup 4: invokespecial #8 // Method Human."<init>":()V 7: astore_1 8: new #9 // class Man 11: dup 12: invokespecial #10 // Method Man."<init>":()V 15: astore_2 16: new #11 // class Woman 19: dup 20: invokespecial #12 // Method Woman."<init>":()V 23: astore_3 24: new #13 // class DispatchTest 27: dup 28: invokespecial #14 // Method "<init>":()V 31: astore 4 33: aload 4 35: aload_1 36: invokevirtual #15 // Method sayHi:(LHuman;)V 39: aload 4 41: aload_3 42: invokevirtual #15 // Method sayHi:(LHuman;)V 45: aload 4 47: aload_2 48: invokevirtual #15 // Method sayHi:(LHuman;)V 51: return LocalVariableTable: Start Length Slot Name Signature 0 52 0 args [Ljava/lang/String; 8 44 1 human LHuman; 16 36 2 man LHuman; 24 28 3 woman LHuman; 33 19 4 test LDispatchTest;
這段字節碼的表義很清晰,就是建立了類型爲Human
的Man
和Woman
的三個類的對象實例,而後在執行test.sayHi(human);
、test.sayHi(woman);
、test.sayHi(man);
的時候都將其編譯成了調用方法sayHi:(LHuman;)V
,即參數類型是Human
的sayHi
方法。從這裏咱們能夠看出來實際上靜態編譯重載方法的時候的只會使用方法參數的靜態類型指定的方法,而實際類型因爲在運行時可能會發生變化,沒有辦法在編譯期得到其實際類型,所以採用了這種方式直接肯定方法的分派結果。
源碼定義了繼承了Human
的Man
和Woman
,關鍵在於看一下編譯器將man.sayHi();
、woman.sayHi();
編譯成了什麼,也就是說靜態階段編譯器對重寫方法的方法分派是什麼樣的。
abstract class Human{ protected abstract void sayHi(); } class Man extends Human{ @Override protected void sayHi() { System.out.println("Hi man"); } } class Woman extends Human{ @Override protected void sayHi() { System.out.println("Hi woman"); } } public class DispatchTest { public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); DispatchTest test = new DispatchTest(); man.sayHi(); woman.sayHi(); } }
javap獲取到的class字節碼解釋:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class Man 3: dup 4: invokespecial #3 // Method Man."<init>":()V 7: astore_1 8: new #4 // class Woman 11: dup 12: invokespecial #5 // Method Woman."<init>":()V 15: astore_2 16: new #6 // class DispatchTest 19: dup 20: invokespecial #7 // Method "<init>":()V 23: astore_3 24: aload_1 25: invokevirtual #8 // Method Human.sayHi:()V 28: aload_2 29: invokevirtual #8 // Method Human.sayHi:()V 32: return LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 8 25 1 man LHuman; 16 17 2 woman LHuman; 24 9 3 test LDispatchTest;
這段字節碼建立了類型爲Man
和Woman
的兩個類的對象實例,而後在執行man.sayHi();
、woman.sayHi();
的時候都將其編譯成了調用方法Human.sayHi:()V
,即Human
類型的sayHi
方法。從這裏咱們能夠看出來實際上靜態編譯重寫方法的時候的只會使用實例對象的靜態類型的方法,而實際類型因爲在運行時可能會發生變化,沒有辦法在編譯期得到其實際類型,所以採用了這種方式直接肯定方法的分派結果。可是在運行時會去獲取這個實例對象的實際類型,而後看這個實際類型是否認了名稱和限定符知足的方法,若是有就會選擇分派到這個方法上。這就是運行時動態分派產生的影響,這種選擇在編譯器是看不出來的。