疯狂涨知识!Java多态实现原理技术总监都拍手叫好
##前言
多態(tài)是Java語言重要的特性之一,它允許基類的指針或引用指向派生類的對象,而在具體訪問時實現(xiàn)方法的動態(tài)綁定。Java對于方法調(diào)用動態(tài)綁定的實現(xiàn)主要依賴于方法表,但通過引用調(diào)用(invokevitual)和接口引用調(diào)用(invokeinterface)的實現(xiàn)則有所不同。
Java多態(tài)實現(xiàn)原理的大致過程:首先是Java編譯器將Java源代碼編譯成class文件。在編譯過程中,會根據(jù)靜態(tài)類型將調(diào)用的符號引用寫到class文件中。在執(zhí)行時,JVM根據(jù)class文件找到調(diào)用方法的符號引用,然后在靜態(tài)類型的方法表中找到偏移量,然后再根據(jù)this指針確定對象的實際類型,使用實際類型的方法表(偏移量跟靜態(tài)類型中的偏移量一樣是指?就是用的靜態(tài)類型中的偏移量,因為符號引用在靜態(tài)類型的方法表中找到的偏移量是同一個),如果在實際的方法中找到該方法(說明參數(shù)值對上了)則直接調(diào)用,否則認為沒有重寫父類的方法則按照繼承關(guān)系從下往上搜索來調(diào)用方法。
程序運行時,需要某個類是,類載入系統(tǒng)會將相應(yīng)的class文件載入到JVM中,并在內(nèi)部建立該類的?類型信息 (這個類型信息其實就是class文件在JVM中存儲的一種數(shù)據(jù)結(jié)構(gòu)),包含java類定義的所有信息(方法代碼、類和成員變量、以及實現(xiàn)動態(tài)調(diào)用的核心 -?方法表 )。這個類型信息存儲在方法區(qū)。
注意:這個方法去中的類型信息跟在堆中存放的class對象是不同的。在方法區(qū)中,這個class的類型信息只有唯一的實例(所以是各個線程共享的內(nèi)存區(qū)域),而在堆中可以有多個該class對象。可以通過堆中的class對象訪問到方法去中的類型信息(像Java的反射機制,通過class對象可以訪問到該類的所有信息)。
【重點】
方法表是實現(xiàn)動態(tài)調(diào)用的核心。上面講過方法表存放在方法區(qū)中的類型信息中。為了優(yōu)化對象調(diào)用方法的速度,方法區(qū)的類型信息會增加一個指針,該指針指向一個記錄該類方法的方法表,方法表中的每一個項都是對應(yīng)方法的指針。
這些方法中包括從父類繼承的所有方法以及自身重寫(override)的方法。
【拓展】
方法區(qū):方法區(qū)和JAVA堆一樣,是各個線程共享的內(nèi)存區(qū)域,用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。?
運行時常量池:它是方法區(qū)的一部分,Class文件中除了有類的版本、方法、字段等描述信息外,還有一項信息是常量池,用于存放編譯器生成的各種符號引用,這部分信息在類加載時進入方法區(qū)的運行時常量池中。?
方法區(qū)的內(nèi)存回收目標是針對常量池的回收及對類型的卸載。
#####Java?的方法調(diào)用方式
Java?的方法調(diào)用有兩類,動態(tài)方法調(diào)用與靜態(tài)方法調(diào)用。
- 靜態(tài)方法調(diào)用是指對于類的靜態(tài)方法的調(diào)用方式,是靜態(tài)綁定的
- 動態(tài)方法調(diào)用需要有方法調(diào)用所作用的對象,是動態(tài)綁定的。
類調(diào)用?(invokestatic)?是在編譯時就已經(jīng)確定好具體調(diào)用方法的情況。
實例調(diào)用?(invokevirtual)則是在調(diào)用的時候才確定具體的調(diào)用方法,這就是動態(tài)綁定,也是多態(tài)要解決的核心問題。
JVM?的方法調(diào)用指令有四個,分別是?invokestatic,invokespecial,invokesvirtual?和?invokeinterface。前兩個是靜態(tài)綁定,后兩個是動態(tài)綁定的。本文也可以說是對于JVM后兩種調(diào)用實現(xiàn)的考察。
方法表與方法調(diào)用
如有類定義?Person, Girl, Boy
class Person {public String toString() {return "I'm a person.";}public void eat() {}public void speak() {} }class Boy extends Person {public String toString() {return "I'm a boy";}public void speak() {}public void fight() {} }class Girl extends Person {public String toString() {return "I'm a girl";}public void speak() {}public void sing() {} }當這三個類被載入到?Java?虛擬機之后,方法區(qū)中就包含了各自的類的信息。Girl?和?Boy?在方法區(qū)中的方法表可表示如下:
可以看到,Girl?和?Boy?的方法表包含繼承自 Object 的方法,繼承自直接父類 Person 的方法及各自新定義的方法。注意方法表條目指向的具體的方法地址,如?Girl?繼承自?Object?的方法中,只有?toString()?指向自己的實現(xiàn)(Girl?的方法代碼),其余皆指向?Object?的方法代碼;其繼承自于?Person?的方法?eat()?和?speak()?分別指向?Person?的方法實現(xiàn)和本身的實現(xiàn)。
如果子類改寫了父類的方法,那么子類和父類的那些同名的方法共享一個方法表項。
因此,方法表的偏移量總是固定的。所有繼承父類的子類的方法表中,其父類所定義的方法的偏移量也總是一個定值。
Person?或?Object中的任意一個方法,在它們的方法表和其子類?Girl?和?Boy?的方法表中的位置 (index) 是一樣的。這樣?JVM?在調(diào)用實例方法其實只需要指定調(diào)用方法表中的第幾個方法即可。
如調(diào)用如下:
class Party {void happyHour() {Person girl = new Girl();girl.speak();} }當編譯?Party?類的時候,生成?girl.speak()的方法調(diào)用假設(shè)為:????Invokevirtual #12
設(shè)該調(diào)用代碼對應(yīng)著?girl.speak(); #12?是?Party?類的常量池的索引。JVM?執(zhí)行該調(diào)用指令的過程如下所示:
(這里有個錯誤,上圖為ClassReference常量池而非Party的常量池)
【再次拓展】
常量池在邏輯上可以分成多個表,每個表包含一類的常量信息,本文只探討對于 Java 調(diào)用相關(guān)的常量池表。
CONSTATNT_Method_info**:**類方法引用表;包含引用的任何類型方法的描述信息,主要包括類信息索引和名字類型索引。
CONSTATNT_Class_info**:**類信息表;包含任何被引用的類或接口的 ‘符號引用’ ,每一個條目主要包含一個索引,指向CONSTA_Utf8_info表,表示該類或接口的全限定名。
CONSTATNT_NameAndType_info:名字類型表;包含引用的任意方法或字段的名稱和描述符信息在字符串常量中的索引。
CONSTATNT_Utf8_info:字符串常量表; 該表包含該類所使用的所有字符串常量,比如代碼中的字符串引用、引用的類名、方法的名字、其他引用的類與方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至該表。
可以看到,給定任意一個方法的索引,在常量池中找到對應(yīng)的條目后,可以得到該方法的類索引(classindex)和名字類型索引 (nameandtypeindex), 進而得到該方法所屬的類型信息和名稱及描述符信息(參數(shù),返回值等)——從而通過對方法的類型信息和名稱及描述符信息(參數(shù),返回值等)來確定具體是調(diào)用哪一個方法。
JVM執(zhí)行??Invokevirtual #12?指令的過程:
(1)在常量池中找到方法調(diào)用的符號引用。?JVM 首先查看 Party(應(yīng)為ClassReference常量池) 的常量池索引為 12 的條目 (此條目即指 -?查看常量池中的CONSTATNT_Method_info表,即類方法引用表),再 進一步查看常量池中的(CONSTANTClassinfo,CONSTANTNameAndTypeinfo ,CONSTANTUtf8info)?三個表。
(2) 可得出要調(diào)用的方法是 Person 的 speak 方法, 查看 Person 的方法表,得出 speak 方法在該方法表中的偏移量 15,這就是該方法調(diào)用的直接引用。
(3)?根據(jù)this指針得到具體的對象(即girl所指向位與堆中的對象)
(4)根據(jù)對象得到該對象對應(yīng)的方法表,根據(jù)偏移量15查看有無重寫(override)該方法,如果重寫,則可以直接調(diào)用(Girl的方法表的speak項指向自身的方法而非父類);如果沒有重寫,則需要拿到按照繼承關(guān)系從下往上的基類(這里是Person類)的方法表,同樣按照這個偏移量15查看有無該方法。
##最后
以上,是對Java多態(tài)實現(xiàn)原理翻閱兩篇博文后為便于理解而整理而出。
參考博文:
https://www.cnblogs.com/kaleidoscope/p/9790766.html
https://zhuanlan.zhihu.com/p/94086109
大家看完有什么不懂的可以在下方留言討論.
謝謝你的觀看。
讀者福利
讀到這的朋友還可以免費領(lǐng)取一份收集的Java進階知識筆記和視頻資料。
資料免費領(lǐng)取方式:關(guān)注后,點擊這里即可免費領(lǐng)取
更多筆記分享
[外鏈圖片轉(zhuǎn)存中…(img-RDB9BwcB-1623502351596)]
更多筆記分享
[外鏈圖片轉(zhuǎn)存中…(img-dSYh4L64-1623502351597)]
[外鏈圖片轉(zhuǎn)存中…(img-pDxH0Vjj-1623502351598)]
總結(jié)
以上是生活随笔為你收集整理的疯狂涨知识!Java多态实现原理技术总监都拍手叫好的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 师傅游戏什么配置能玩
- 下一篇: 幸福花开剧情介绍