Java方法調(diào)用解析靜態(tài)分派動(dòng)態(tài)分派執(zhí)行過(guò)程
方法調(diào)用
在程序運(yùn)行時(shí),進(jìn)行方法調(diào)用是最普遍,最頻繁的操作
方法調(diào)用不等于方法執(zhí)行:
- 方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用的方法版本,即調(diào)用哪一個(gè)方法
- 不涉及方法內(nèi)部的具體運(yùn)行過(guò)程
Class文件的編譯過(guò)程不包括傳統(tǒng)編譯中的連接步驟
Class文件中的一切方法調(diào)用在Class文件里面存儲(chǔ)的都是符號(hào)引用,而不是方法在在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址,即之前的直接引用:
- 這樣使得Java具有更強(qiáng)大的動(dòng)態(tài)擴(kuò)展能力
- 同時(shí)也使得Java方法調(diào)用過(guò)程變得相對(duì)復(fù)雜
- 需要在類(lèi)加載期間,甚至?xí)竭\(yùn)行期間才能確定目標(biāo)方法的直接引用
方法解析
所有方法調(diào)用中的目標(biāo)方法在Class文件里都是一個(gè)常量池的引用
在類(lèi)的加載解析階段,會(huì)將其中的一部分符號(hào)引用轉(zhuǎn)化為直接引用:
方法在程序真正執(zhí)行之前就有一個(gè)可確定的調(diào)用版本,并且這個(gè)方法的調(diào)用版本在運(yùn)行期是不可改變的
也就是說(shuō),調(diào)用目標(biāo)在程序代碼中完成,編譯器進(jìn)行編譯時(shí)就必須確定下來(lái),這也叫做方法解析
Java方法分類(lèi)
在Java中符合 "編譯期可知,運(yùn)行期不可變" 的方法有兩大類(lèi):
- 靜態(tài)方法: 與類(lèi)型直接關(guān)聯(lián)
- 私有方法: 在外部不可被訪問(wèn)
這兩種方法各自的特點(diǎn)決定這兩種方法都不可能通過(guò)繼承或者別的方式重寫(xiě)版本,因此適合在類(lèi)加載階段進(jìn)行解析
非虛方法: 在類(lèi)加載階段會(huì)把符號(hào)引用解析為該方法的直接引用
- 靜態(tài)方法
- 私有方法
- 實(shí)例構(gòu)造器
- 父類(lèi)方法
虛方法: 在類(lèi)加載階段不會(huì)將符號(hào)引用解析為該方法的直接引用
除去以上的非虛方法,其它的方法均為虛方法
靜態(tài)分派
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public static void sayHello(Human guy) {
System.out.println("Hello, Guy!");
}
public static void sayHello(Man guy) {
System.out.println("Hello, Gentleman!");
}
public static void sayHello(woman guy) {
System.out.println("Hello, Lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human women = new Woman();
sayHello(man);
sayHello(woman);
}
}Human man = new Human();
Human為變量的靜態(tài)類(lèi)型
Man為變量的實(shí)際類(lèi)型
靜態(tài)類(lèi)型和實(shí)際類(lèi)型在程序中都會(huì)放生變化:
靜態(tài)類(lèi)型:
- 靜態(tài)類(lèi)型的變化僅僅在使用時(shí)發(fā)生
- 變量本身的靜態(tài)類(lèi)型不會(huì)被改變
- 最終的靜態(tài)類(lèi)型在編譯器中可知
實(shí)際類(lèi)型:
- 實(shí)際類(lèi)型變化的結(jié)果在運(yùn)行期才確定下來(lái)
- 編譯器在編譯期間并不知道一個(gè)對(duì)象的實(shí)際類(lèi)型是什么
Human human = new Man(); sayHello(man); sayHello((Man)man); // 類(lèi)型轉(zhuǎn)換,靜態(tài)類(lèi)型變化,轉(zhuǎn)型后的靜態(tài)類(lèi)型一定是Man man = new woman(); // 實(shí)際類(lèi)型變化,實(shí)際類(lèi)型是不確定的 sayHello(man); sayHello((Woman)man); // 類(lèi)型轉(zhuǎn)換,靜態(tài)類(lèi)型變化
編譯器在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類(lèi)型而不是實(shí)際類(lèi)型作為判斷依據(jù),靜態(tài)類(lèi)型在編譯期間可以知道:
編譯階段,Javac編譯器會(huì)根據(jù)參數(shù)的靜態(tài)類(lèi)型決定使用哪個(gè)重載版本
靜態(tài)分派:
- 所有依賴靜態(tài)類(lèi)型來(lái)定位方法的執(zhí)行版本的分派動(dòng)作
- 典型應(yīng)用 :方法重載
靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的的動(dòng)作不是由虛擬機(jī)執(zhí)行的,而是由編譯器完成的
由于字面量沒(méi)有顯示靜態(tài)類(lèi)型,只能通過(guò)語(yǔ)言上的規(guī)則去理解和推斷
public class LiteralTest {
public static void sayHello(char arg) {
System.out.println("Hello, char!");
}
public static void sayHello(int arg) {
System.out.println("Hello, int!");
}
public static void sayHello(long arg) {
System.out.println("Hello, long!");
}
public static void sayHello(Character arg) {
System.out.println("Hello, Character!");
}
public static void main(String[] arg) {
sayHello('a');
}
}編譯器將重載方法從上向下依次注釋,得到不同的輸出
如果編譯器無(wú)法確定要自定轉(zhuǎn)型為哪種類(lèi)型,會(huì)提示類(lèi)型模糊,拒絕編譯
public class LiteralTest {
public static void sayHello(String arg) { // 新增重載方法
System.out.println("Hello, String!");
}
public static void sayHello(char arg) {
System.out.println("Hello, char!");
}
public static void sayHello(int arg) {
System.out.println("Hello, int!");
}
public static void sayHello(long arg) {
System.out.println("Hello, long!");
}
public static void sayHello(Character arg) {
System.out.println("Hello, Character!");
}
public static void main(String[] args) {
Random r = new Random();
String s = "abc";
int i = 0;
sayHello(r.nextInt() % 2 != 0 ? s : 1 ); // 編譯錯(cuò)誤
sayHello(r.nextInt() % 2 != 0 ? 'a' : false); //編譯錯(cuò)誤
}
}動(dòng)態(tài)分派
public class DynamicDispatch {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@override
protected void sayHello() {
System.out.println("Man Say Hello!");
}
}
static class Woman extends Human {
@override
protected void sayHello() {
System.out.println("Woman Say Hello!");
}
}
public static void main(String[] args) {
Human man = new Man();
Human women = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}這里不是根據(jù)靜態(tài)類(lèi)型決定的
- 靜態(tài)類(lèi)型的Human兩個(gè)變量man和woman在調(diào)用sayHello() 方法時(shí)執(zhí)行了不同的行為
- 變量man在兩次調(diào)用中執(zhí)行了不同的方法
導(dǎo)致這個(gè)現(xiàn)象的額原因 :這兩個(gè)變量的實(shí)際類(lèi)型不同
Java虛擬機(jī)是如何根據(jù)實(shí)際類(lèi)型分派方法的執(zhí)行版本的: 從invokevirtual指令的多態(tài)查找過(guò)程開(kāi)始 ,invokevirtual指令運(yùn)行時(shí)解析過(guò)程大致分為以下幾個(gè)步驟:
- 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素所指向的對(duì)象的實(shí)際類(lèi)型,記作C
- 如果在類(lèi)型C中找到與常量中的描述符和簡(jiǎn)單名稱相符合的方法,然后進(jìn)行訪問(wèn)權(quán)限驗(yàn)證,如果驗(yàn)證通過(guò)則返回這個(gè)方法的直接引用,查找過(guò)程結(jié)束;如果驗(yàn)證不通過(guò),則拋出java.lang.illegalAccessError異常
- 如果未找到,就按照繼承關(guān)系從下往上依次對(duì)類(lèi)型C的各個(gè)父類(lèi)進(jìn)行第二步的搜索和驗(yàn)證過(guò)程
- 如果始終沒(méi)有找到合適的方法,則拋出java.lang.AbstractMethodError異常
Java語(yǔ)言方法重寫(xiě)的本質(zhì):
invokevirtual指令執(zhí)行的第一步就是在運(yùn)行時(shí)期確定接收者的實(shí)際類(lèi)型,所以兩次調(diào)用中的invokevirtual指令把常量池中的類(lèi)方法符號(hào)引用解析到了不同的直接引用上
這種在運(yùn)行時(shí)期根據(jù)實(shí)際類(lèi)型確定方法執(zhí)行版本的分派過(guò)程就叫做動(dòng)態(tài)分派
虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn)
虛擬機(jī)概念解析的模式就是靜態(tài)分派和動(dòng)態(tài)分派,可以理解虛擬機(jī)在分派中 "會(huì)做什么" 這個(gè)問(wèn)題
虛擬機(jī) "具體是如何做到的" 在各種虛擬機(jī)實(shí)現(xiàn)上會(huì)有差別:
- 由于動(dòng)態(tài)分派是非常頻繁的動(dòng)作,而且動(dòng)態(tài)分派的方法版本選擇過(guò)程需要運(yùn)行時(shí)在類(lèi)的方法元數(shù)據(jù)中搜索合適的目標(biāo)方法
- 因此在虛擬機(jī)的實(shí)際實(shí)現(xiàn)中,為了基于性能的考慮,大部分實(shí)現(xiàn)都不會(huì)真正的進(jìn)行如此頻繁的搜索
- 最常用的"穩(wěn)定優(yōu)化"的方式是為類(lèi)在方法區(qū)中建立一個(gè)虛方法表(Virtual Method Table,即vtable), 使用虛方法表索引代替元數(shù)據(jù)查找以提高性能
虛方法表中存放著各個(gè)方法的實(shí)際入口地址:
- 如果某個(gè)方法在子類(lèi)中沒(méi)有被重寫(xiě),那子類(lèi)的虛方法表里面的地址入口和父類(lèi)相同方法的地址入口是一致的,都指向父類(lèi)的實(shí)際入口
- 如果子類(lèi)中重寫(xiě)了這個(gè)方法,子類(lèi)方法表中的地址將會(huì)替換為指向子類(lèi)實(shí)際方法的入口地址
具有相同簽名的方法,在父類(lèi),子類(lèi)的虛方法表中具有一樣的索引序號(hào):
這樣當(dāng)類(lèi)型變換時(shí),僅僅需要變更查找的方法表,就可以從不同的虛方法表中按索引轉(zhuǎn)換出所需要的入口地址
方法表一般在類(lèi)加載階段的連接階段進(jìn)行初始化:
準(zhǔn)備了類(lèi)的變量初始值后,虛擬機(jī)會(huì)把該類(lèi)的方法表也初始化完畢
以上就是Java方法調(diào)用解析靜態(tài)分派動(dòng)態(tài)分派執(zhí)行過(guò)程的詳細(xì)內(nèi)容,更多關(guān)于Java靜態(tài)動(dòng)態(tài)分派執(zhí)行過(guò)程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- java調(diào)用WebService服務(wù)的四種方法總結(jié)
- Java對(duì)象方法的調(diào)用執(zhí)行過(guò)程詳解
- java反射調(diào)用方法NoSuchMethodException的解決方案
- Java多線程異步調(diào)用性能調(diào)優(yōu)方法詳解
- java異步調(diào)用的4種實(shí)現(xiàn)方法
- Java 帶參數(shù)與帶返回值的方法的定義和調(diào)用
- 詳解Java方法method的定義與調(diào)用及重載
- Java多線程run方法中直接調(diào)用service業(yè)務(wù)類(lèi)應(yīng)注意的問(wèn)題及解決
- 簡(jiǎn)單解析java方法在調(diào)用在內(nèi)存中的執(zhí)行過(guò)程
相關(guān)文章
Java?IO流之StringWriter和StringReader用法分析
這篇文章主要介紹了Java?IO流之StringWriter和StringReader用法分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
JVM自定義類(lèi)加載器在代碼擴(kuò)展性實(shí)踐分享
這篇文章主要介紹了JVM自定義類(lèi)加載器在代碼擴(kuò)展性實(shí)踐分享,一個(gè)類(lèi)型從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期將會(huì)經(jīng)歷加載、驗(yàn)證、準(zhǔn)備、解析、初始化 、使用和卸載七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱為連接2022-06-06
struts2.5+框架使用通配符與動(dòng)態(tài)方法常見(jiàn)問(wèn)題小結(jié)
這篇文章主要介紹了struts2.5+框架使用通配符與動(dòng)態(tài)方法常見(jiàn)問(wèn)題 ,在文中給大家提到了Struts2.5框架使用通配符指定方法 ,需要的朋友可以參考下2018-09-09
詳解Springboot集成sentinel實(shí)現(xiàn)接口限流入門(mén)
這篇文章主要介紹了詳解Springboot集成sentinel實(shí)現(xiàn)接口限流入門(mén),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
POI XSSFSheet shiftRows bug問(wèn)題解決
這篇文章主要介紹了POI XSSFSheet shiftRows bug問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

