前言
class文件被加載到內存中,JVM如何執行類方法?下面我們來看一下方法的人生。
重寫與重載
重寫與重載對與我們並不陌生,java作爲面向對象的語言,有封裝、繼承、多態三大特性,而重寫是多態的一個體現,而重載是參數不同的同名方法。
重載
但是對於JVM並沒有重載這個概念,在源代碼編譯時就已經指定了實際的方法,那麼指定的具體過程是怎麼樣的呢?編譯器根據傳入參數的聲明類型(注意與實際類型區分)來選擇重載,選取分爲三個階段:
- 不考慮自動拆裝箱以及可變長參數的情況下選取方法
- 允許自動拆裝箱,但是不允許可變長參數
- 允許自動拆裝箱和可變長參數
重寫
而重寫會根據調用者的動態類型選取實際的目標方法。JVM識別方法主要通過類名、方法名、以及方法描述符(方法描述符由方法的參數類型以及返回類型構成)。JVM對於方法重寫的判定也基於方法描述符,子類定義了與父類非私有、非靜態的方法、當方法描述符相同時判定爲重寫。
方法的調用
字節碼中與調用相關的指令有五種:
-
invokestatic:調用靜態方法,
-
invokespecial:調用私有實例方法,構造器,以及supper調用父類的實例方法或構造器和所以實現接口的默認方法
-
invokevirtual:調用非私有實例方法
-
invokeinterfance:調用接口方法
-
invokedynamic:調用動態方法
編譯過程中,不知道目標方法的內存地址,用符號引用來表示,符號引用包括目標方法的類或接口名字,以及目標方法的方法名和方法描述符,符號引用存入class文件的常量池中,根據目標方法分爲接口引用和非接口引用,在執行解析時會將符號引用替換爲實際引用。
非接口引用,假如符號引用指向類C:
- 在C中查找符號名字及描述符的方法
- 如果沒有找到,C的父類中搜索,一直到Object
- 如果沒有找到,C的直接實現或間接實現的接口搜索,這一步得到的目標方法是非私有非靜態,如果目標方法在間接實現的接口中,則需滿足C與該接口之間沒有其他符合條件的目標方法,如果多個符符合,任意返回
接口引用,假設執行接口I:
- I中查找符合名字和描述符的方法
- 在Object的公有實例中搜索
- 在I的超接口中搜索,要求與非接口的引用一致
經過對符號引用的的解析,符號引用解析成了實際引用,可以靜態綁定的方法,實際引用是指向方法的指針。需要動態綁定的方法調用,實際引用是一個方法表的索引。
方法表
在寫類加載機制的那篇博客中曾經說過,在類加載的準備階段有的虛擬機會生成一個類的方法表,這個方法表就是用來動態確定方法調用的。
方法表其實就是一個數組,每個元素都指向當前類或者祖先類中非私有的實例方法,但是方法表還需要滿足兩個條件:子類包含父類的所有方法,並且子類重寫的方法與父類的方法索引要相同。在實際的調用過程中,JVM根據調用者的實際類型在方法表中獲取目標方法
當然方法表作爲定位目標方法的一種手段僅存在解析執行中,在即時編譯下還有更好的方法比如:內聯緩存、方法內聯