1. 程式人生 > >JVM類載入的符號解析

JVM類載入的符號解析

典型的一個類中,主要是static欄位,類欄位,static方法,類方法這四種存在。 至於static欄位和類欄位的初始化賦值語句,看似有點特別,其實在編譯後歸入<clinit>方法和<init>方法。 對於一個方法,形式引數實際上在呼叫方法之前會被壓棧,進入運算元棧,這是棧幀的一部分。 方法的區域性變數,如果是物件,物件本身的資料儲存在堆裡,棧幀的區域性變量表中儲存的是物件的reference(引用)。在解析之前是符號引用,解析之後變成直接引用(就是一個方法或者物件的首地址)。

方法符號引用

在class檔案中,對於方法呼叫時是符號引用,並沒有解析為方法地址。靜態方法和物件方法的符號規則並沒有區別,他們的區別在於呼叫的操作指令,一個是invokestatic,另一個是invokevirtual。後者在呼叫指令之前會把this物件壓入運算元棧。在解析類方法或者介面方法符號的時候,符號中包含類名,但是為了實現多型應該不是根據這個類名,而是查詢this所指物件的Class物件定位到的符號表,根據符號所包含類名找到對應方法表,找到特定方法簽名,如果找不到則遞迴尋找父類的方法表,還有更詳細的安全檢查,介面方法也是根據this去查詢,找不到時也會做更多檢查。

如果是invokestatic的符號,那麼只會根據符號中的類名知道的Class查詢方法表。

類或者介面的符號引用

很奇怪對於程式碼

  #62 = Class              #63            // java/util/Set
  #63 = Utf8               java/util/Set
    ……
    29: ldc           #62                 // class java/util/Set
    31: astore        5
    33: aload         5
    35: invokevirtual #33                 // Method java/lang/Class.getName:()Ljava/lang/String;

offset=29的指令ldc只是把#62所代表的字串常量壓棧(運算元棧,後文不加說明皆是此意) offset=31的指令astore只是把棧頂彈出儲存到index=5的區域性變量表 offset=33就是在載入變數壓棧 offset=35的指令是呼叫虛方法了,此時棧頂代表的含義必須是物件方法所需的this引數,但是此時棧頂貌似只是字串常量啊? 問題就在這裡,從位元組碼看起來區域性變數儲存的也是字串常量,也不符合原始碼中變數型別是Class物件引用。實際上這裡JVM悄悄做了符號解析,JLS沒有規定JVM必須在載入位元組碼時對常量池中符號進行解析或者執行到相關位元組碼時進行解析。但是真正執行到時,必須是符號已經被解析。 ==== 題外話,這樣也說明Class cls = String.class這樣的語句,cls變數其實是解析出來的地址。 而Class clssss = obj.getClass()是呼叫Java方法,傳遞真實引用到區域性變數。 ==== 說到常量池,主要是因為多個class中的常量池會有相同的字串,因此,設計為不可變,合併到JVM統一的常量池有利於節省空間。

欄位符號解析

     9: sipush        888
    12: putfield      #17                 // Field df:I

常量池索引17代表的是欄位符號,看起來putfield指令直接把棧頂的數字888儲存到了#17的常量池,其實當然不可能,putfield的真實含義就是儲存到一個field,欄位的符號引用在執行之前已經被解析為欄位地址。如果是putstatic,那麼JVM可以根據類的欄位變量表解析出欄位符號,如果是物件欄位,也可以。

本文希望是,理解JVM解析符號的概念,為什麼要解析符號?有哪些型別的符號需要解析?JVM解析符號的大致過程是什麼?