1. 程式人生 > >JVM學習筆記(二)--方法調用之靜態分配和動態分配

JVM學習筆記(二)--方法調用之靜態分配和動態分配

extends AD 找到 n! sse 運行時 面試 static sys

本篇文章從JVM的角度來理解Java學習中經常提到的重載和重寫。

方法調用:方法調用不等同於方法執行,在Java虛擬機中,方法調用僅僅是為了確定調用哪個版本的方法。方法調用分為解析調用和分派。解析調用一定是靜態的,而分派可以是靜態的,也可以是動態的。我們這裏只介紹分派中的靜態分配和動態分配。

  • 靜態分配所有依賴靜態類型來定位方法執行版本的分派動作稱為靜態分配。

下面看個例子,順便來猜一下結果(面試中經常遇到):

 1 class Human {
 2     
 3 }
 4 
 5 class Man extends Human{
 6     
 7 }
 8 
 9 class
Woman extends Human { 10 11 } 12 13 public class overLoadTest { 14 15 public void sayHello(Human guy){ 16 System.out.println("Hello guy!"); 17 } 18 public void sayHello(Man man){ 19 System.out.println("Hello man!"); 20 } 21 public void sayHello(Woman woman){
22 System.out.println("Hello woman!"); 23 } 24 25 26 public static void main(String[] args){ 27 Human man = new Man(); 28 Human woman = new Woman(); 29 30 overLoadTest sr = new overLoadTest(); 31 32 sr.sayHello(man); 33 sr.sayHello(woman);
34 35 } 36 }

運行結果如下:

技術分享圖片

如果不明白為什麽是這樣的結果,我們先來了解一下,什麽是靜態類型和實際類型。

Human man = new Man();
Human woman = new Woman();

這裏Human為靜態類型,man和woman為實際類型。區別為靜態類型的變化僅僅在使用時發生,變量本身的靜態類型不會改變,並且最終的靜態類型是在編譯期間可知的;而實際類型變化的結果在運行期間才可確定,編譯器在編譯程序時並不知道一個對象的實際類型是什麽。


靜態分配最典型的應用就是方法重載。我們再來復習一下方法重載的定義:在一個類中有多個方法,名稱相同但參數列表不同(參數個數,參數類型,參數順序)。上面例子就是方法重載。這裏的運行結果毫不意外。


sr.sayHello(man);
sr.sayHello(woman);

man和woman的靜態類型都是Human。

  • 動態分配 :所有依賴實際類型來定位方法執行版本的分派動作稱為靜態分配。

動態分配的典型應用為重寫。下面為重寫的例子

 1 abstract class Human_Test {
 2     public abstract void sayHello();
 3 }
 4 
 5 class Man_Test extends Human_Test{
 6      @Override  
 7     public void sayHello() {
 8         System.out.println("Hello man!");
 9     }
10     
11 }
12 
13 class Woman_Test extends Human_Test {
14      @Override  
15     public void sayHello() {
16         System.out.println("Hello woman!");
17     }
18     
19 }
20 
21 public class OverrideTest {
22     public static void main(String[] args){
23         Human_Test man = new Man_Test();
24         Human_Test woman = new Woman_Test();
25         
26         man.sayHello();  
27         woman.sayHello();  
28               
29         man = new Woman_Test();  
30         man.sayHello(); 
31     }
32     
33     
34     
35 }

運行結果:

技術分享圖片

因為動態分配是根據實際類型來確定要調用的方法的,這樣的結果也比較符合我們的思維習慣。那麽JVM又是怎麽根據實際類型來找到目標方法的呢?用Javap來分析一下字節碼:

技術分享圖片

看上圖17和21行,可以看到已經找到了Human中的sayHello(),但是invokevirtual指令是多態查找指令。其過程如下:

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

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

3). 否則,按照繼承關系從下往上一次對C的各個父類進行第二步驟的搜索和驗證過程。

4). 如果始終還是沒有找到合適的方法直接引用,則拋出java.lang.AbstractMethodError異常。

由於invokevirtual指令執行的第一步是在運行時確定接收者的實際類型,所以兩次中的invokevirtual指令把常量池中的類方法符號引用解析到不同的直接引用上,這個就是java語言中方法重寫的本質。

JVM學習筆記(二)--方法調用之靜態分配和動態分配