日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM方法调用说明

發(fā)布時間:2023/12/20 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM方法调用说明 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文鏈接:http://www.jianshu.com/p/56a7c4b26b14

前言

Java具備三種特性:封裝、繼承、多態(tài)。
Java文件在編譯過程中不會進(jìn)行傳統(tǒng)編譯的連接步驟,方法調(diào)用的目標(biāo)方法以符號引用的方式存儲在Class文件中,這種多態(tài)特性給Java帶來了更靈活的擴(kuò)展能力,但也使得方法調(diào)用變得相對復(fù)雜,需要在類加載期間,甚至到運(yùn)行期間才能確定目標(biāo)方法的直接引用。

方法調(diào)用

所有方法調(diào)用的目標(biāo)方法在Class文件里面都是常量池中的符號引用。在類加載的解析階段,如果一個方法在運(yùn)行之前有確定的調(diào)用版本,且在運(yùn)行期間不變,虛擬機(jī)會將其符號引用解析為直接調(diào)用。

這種 編譯期可知,運(yùn)行期不可變 的方法,主要包括靜態(tài)方法和私有方法兩大類,前者與具體類直接關(guān)聯(lián),后者在外部不可訪問,兩者都不能通過繼承或別的方式進(jìn)行重寫。

JVM提供了如下方法調(diào)用字節(jié)碼指令:

  • invokestatic:調(diào)用靜態(tài)方法;
  • invokespecial:調(diào)用實例構(gòu)造方法,私有方法和父類方法;
  • invokevirtual:調(diào)用虛方法;
  • invokeinterface:調(diào)用接口方法,在運(yùn)行時再確定一個實現(xiàn)此接口的對象;
  • invokedynamic:在運(yùn)行時動態(tài)解析出調(diào)用點限定符所引用的方法之后,調(diào)用該方法;
  • 通過invokestatic和invokespecial指令調(diào)用的方法,可以在解析階段確定唯一的調(diào)用版本,符合這種條件的有靜態(tài)方法、私有方法、實例構(gòu)造器和父類方法4種,它們在類加載時會把符號引用解析為該方法的直接引用。

    invokestatic

    class StaticTest {public static void hello() {System.out.println("hello");}public static void main(String args[]) {hello();} }

    通過javap命令查看main方法字節(jié)碼

    可以發(fā)現(xiàn)hello方法是通過invokestatic指令調(diào)用的。

    invokespecial

    class VirtualTest {private int id;public static void main(String args[]) {new VirtualTest();} }

    通過javap命令查看main方法字節(jié)碼

    可以發(fā)現(xiàn)實例構(gòu)造器是通過invokespecial指令調(diào)用的。

    通過invokestatic和invokespecial指令調(diào)用的方法,可以稱為非虛方法,其余情況稱為虛方法,不過有一個特例,即被final關(guān)鍵字修飾的方法,雖然使用invokevirtual指令調(diào)用,由于它無法被覆蓋重寫,所以也是一種非虛方法。

    非虛方法的調(diào)用是一個靜態(tài)的過程,由于目標(biāo)方法只有一個確定的版本,所以在類加載的解析階段就可以把符合引用解析為直接引用,而虛方法的調(diào)用是一個分派的過程,有靜態(tài)也有動態(tài),可分為靜態(tài)單分派、靜態(tài)多分派、動態(tài)單分派和動態(tài)多分派。

    靜態(tài)分派

    靜態(tài)分派發(fā)生在代碼的編譯階段。

    public class StaticDispatch {static abstract class Humnan {}static class Man extends Humnan {}static class Woman extends Humnan {}public void hello(Humnan guy) {System.out.println("hello, Humnan");}public void hello(Man guy) {System.out.println("hello, Man");}public void hello(Woman guy) {System.out.println("hello, Woman");}public static void main(String[] args) {Humnan man = new Man();Humnan woman = new Woman();StaticDispatch dispatch = new StaticDispatch();dispatch.hello(man);dispatch.hello(woman);} }

    運(yùn)行結(jié)果:

    hello, Humnan hello, Humnan

    相信有經(jīng)驗的同學(xué)看完代碼后就能得出正確的結(jié)果,但為什么會這樣呢?先看看main方法的字節(jié)碼指令

    通過字節(jié)碼指令,可以發(fā)現(xiàn)兩次hello方法都是通過invokevirtual指令進(jìn)行調(diào)用,而且調(diào)用的是參數(shù)為Human類型的hello方法。

    Humnan man = new Man();

    上述代碼中,變量man擁有兩個類型,一個靜態(tài)類型Human,一個實際類型Man,靜態(tài)類型在編譯期間可知。
    在編譯階段,Java編譯器會根據(jù)參數(shù)的靜態(tài)類型決定調(diào)用哪個重載版本,但在有些情況下,重載的版本不是唯一的,這樣只能選擇一個“更加合適的版本”進(jìn)行調(diào)用,所以不建議在實際項目中使用這種模糊的方法重載。

    動態(tài)分派

    在運(yùn)行期間根據(jù)參數(shù)的實際類型確定方法執(zhí)行版本的過程稱為動態(tài)分派,動態(tài)分派和多態(tài)性中的重寫(override)有著緊密的聯(lián)系。

    public class DynamicDispatch {static abstract class Humnan {abstract void say();}static class Man extends Humnan {@Overridevoid say() {System.out.println("hello, i'm Man");}}static class Woman extends Humnan {@Overridevoid say() {System.out.println("hello, i'm Woman");}}public static void main(String[] args) {Humnan man = new Man();Humnan woman = new Woman();man.say();woman.say();} }

    運(yùn)行結(jié)果:

    hello, i'm Man hello, i'm Woman

    對于習(xí)慣了面向?qū)ο笏季S的同學(xué)對于這個結(jié)果應(yīng)該是理所當(dāng)然的。這種情況下,顯然不能再根據(jù)靜態(tài)類型來決定方法的調(diào)用了,導(dǎo)致不同輸出結(jié)果的原因很簡單,man和woman的實際類型不同,但是JVM如何根據(jù)實際類型決定需要調(diào)用哪個方法?

    main方法的字節(jié)碼指令

    1.字節(jié)碼0 ~ 15行對應(yīng)以下代碼:

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

    在Java堆上申請內(nèi)存空間和實例化對象,并將這兩個實例的引用分別存放到局部變量表的第1、2位置的Slot中。

    2.字節(jié)碼16~21行對應(yīng)以下代碼:

    man.say(); woman.say();

    16和20行指令分別把之前存放到局部變量表1、2位置的對象引用壓入操作數(shù)棧的棧頂,這兩個對象是執(zhí)行say方法的接收者(Receiver),17和21行指令進(jìn)行方法調(diào)用。

    可以發(fā)現(xiàn),17和21兩條指令完全一樣,但最終執(zhí)行的目標(biāo)方法卻不相同,這得從invokevirtual指令的多態(tài)查找說起了,invokevirtual指令在運(yùn)行時分為以下幾個步驟:

  • 找到操作數(shù)棧的棧頂元素所指向的對象的實際類型,記為C;
  • 如果C中存在描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限驗證,如果驗證通過,則直接返回這個方法的直接引用,否則返回java.lang.IllegalAccessError異常;
  • 如果C中不存在對應(yīng)的方法,則按照繼承關(guān)系對C的各個父類進(jìn)行第2步的操作;
  • 如果各個父類也沒對應(yīng)的方法,則返回異常;
  • 所以上述兩次invokevirtual指令將相同的符號引用解析成了不同對象的直接引用,這個過程就是Java語言中重寫的本質(zhì)。

    JVM動態(tài)分派實現(xiàn)

    由于動態(tài)分派是非常頻繁的動作,因此在虛擬機(jī)的實際實現(xiàn)中,會基于性能的考慮,并不會如此頻繁的搜索對應(yīng)方法,一般會在方法區(qū)中建立一個虛方法表,使用虛方法表代替方法查詢以提高性能。

    虛方法表在類加載的連接階段進(jìn)行初始化,存放著各個方法的實際入口地址,如果某個方法在子類中沒有被重寫,那么子類的虛方法表中該方法的入口地址和父類保持一致。

    abstract class Humnan {abstract void say();void run() {System.out.println("Human is run");} } class Man extends Humnan {@Overridevoid say() {System.out.println("hello, i'm Man");}@Overridevoid run() {System.out.println("Man is run");} } class Woman extends Humnan {@Overridevoid say() {System.out.println("hello, i'm Humnan");} }

    對應(yīng)的虛方法表結(jié)構(gòu)

    由于在Woman類中沒有重寫run方法,因此在Woman的虛方法表中,run方法直接指向Human實例。

    總結(jié)

    以上是生活随笔為你收集整理的JVM方法调用说明的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。