Java虛擬機器(十六)--類方法解析
Java方法的解析大體上分為3步:
- 在Java原始碼編譯期間,編譯器負責將Java類原始碼翻譯為對應的位元組碼指令,同時完成的工作還有Java方法的區域性變量表的計算,以及最大運算元棧的計算。
- 在jvm執行期間,jvm載入型別,呼叫classFileParser:parseClassFile()函式對Java class位元組碼檔案進行解析,這一步將會完成Java方法的解析、位元組碼指令存放、父類與介面類方法繼承與過載等一系列邏輯。
- 在呼叫系統類載入器SCL對應用程式的Java類進行載入過程中,完成方法符號連結、驗證,最重要的是完成vtable與itable的構建,從而支援在jvm執行期的方法動態繫結。
對於步驟2,解析方法時,先從位元組碼檔案中獲取方法總數。 然後遍歷每個方法,建立相應的mehodoop(也就是方法物件)。 建立方法物件的步驟主要有以下幾步:
- 讀取和驗證Java方法的訪問標識、名稱以及描述符
- 解析方法的屬性
- 建立methodoop
- 複製位元組碼
方法的訪問標識,名稱、描述符構成方法的簽名。這3個各佔2個位元組。
屬性常見的有Code,LVT(區域性變量表),具體內容可以看十三篇的附錄。
methodOop物件中儲存方法的訪問標識,虛方法表索引,方法大小,方法名等資訊,內部constMethodOop儲存了方法的位元組碼,區域性變量表,異常表,位元組碼與原始碼行對應值、最大運算元棧等資訊。
< init>方法是編譯器自動生成的構造方法。 在沒有明確定義構造方法時,init方法就是構造方法,所有類變數非靜態的都在這裡初始化。所有非靜態程式碼塊的內容也在這執行
其實在位元組碼層次上,所有的構造方法都是叫init,只是引數不同。不管是手工生成的,還是編譯器自動生成的。 如果定義了多個構造方法,那麼每個構造方法其實都會初始化類變數。 當然,一個構造方法明確呼叫另一個構造方法的話,只會在被呼叫的構造方法中做類變數的初始化工作。
< clint>方法也是編譯器自動生成的,執行所有靜態變數的初始化以及靜態程式碼塊。
vtable(虛擬函式表)是Java多型中的一個十分關鍵的技術。
在jvm載入類的過程中,會動態解析Java類的方法及其對父類方法的重寫,進而構建出一個vtable分配到instanceKlass記憶體區的尾部,從而支援執行期動態繫結。
計算vtable主要分為2步:
- 獲取父類vtable大小,並將當前類的vtable的設定為父類vtable的大小
- 迴圈遍歷當前Java類的每一個方法,並呼叫need_new_vtable_entry()函式判斷,如果是true,則vtable增加1
need_vtable_entry():
- 檢查方法有沒有被final,static修飾,或者是建構函式,或者類被final修飾。滿足一條都返回false 2.檢查方法是否被private修飾,如果是則返回true
- 檢查父類有沒有這個方法,父類方法是不是public或者protect的。如果是,也就是說子類重寫了父類的方法,返回false
如果Java類中聲明瞭一個非private,非final,非static的方法:如果該方法是對父類方法的重寫,那麼就直接更新從父類繼承來的vtable的方法指標; 如果不是對父類方法的重寫,那麼向這個類的vtable中新增一個位置,指向該方法的記憶體位置。
vtable分配在instanceKlass物件例項末尾,而instance在jdk8中固定佔用0x1b8大小,所以得到類對應的InstanceKlass例項地址,就可以得到虛方法表。 比如有下面2個類, Test1和p
public class Test1 extends p{
public int a=0;
static Integer si=6;
String s="Hello world";
public Test1(int d){
}
final void do1(){
a++;
System.out.println(a);
}
public static void main(String[] args) {
Test1 test1=new Test1(4);
test1.do1();
test1.a=8;
si=9;
int d=si;
}
private void test(){
this.a=a;
}
}
class p{
void do1(){}
}
是hsdb檢視記憶體,可以發現 Test1的虛方法表長度為7 父類p的虛方法表長度為6 檢視記憶體中相應的地址,可以看到從object類中繼承來的前5個方法都是一樣的,後面的方法不一樣。
檢視父類p中第6個方法,對應的名字,是常量池第11個元素,也就是do1 子類Test1中虛方法表中最後一個方法就是那個私有方法test()。
miranda方法 指那些該類所實現的介面(間接實現也算)中沒有在當前類中實現的方法。會被新增到vtable中,當然這個類一定是abtract的,不然必須要實現。 如果一個米蘭達方法已經在父類的vtable中了,那麼子類不需要新增,只需要修改就行了。
那麼vtable中會有哪些方法呢? 當前類中宣告的方法method,
如果method是被static修飾,那麼一定不在。 如果被final修飾,那要看父類的vtable中有沒有這個方法,有的話就把地址替換(因為子類會先複製父類的vtable)。沒有的話,那麼這個方法就不在vtable中,因為是這個類獨有的,不會被覆蓋,也就不需要轉發。
從修飾符的角度來說private修飾的方法,一定在vtable中,而且是新增的。