概述:
規範而獨立的類檔案結構以及只與類檔案關聯的虛擬機器為Java實現了平臺無關性,甚至還帶來了一些語言無關性。
只要將原始碼編譯為Class檔案規定的格式,JVM就可以執行。
JVM的指令描述能力比Java更強,這使得JVM可以執行不同於Java語言特性的語言。
1、Class檔案整體結構
以位元組為基本單位,無分隔符,大端(低地址存高位)。
無論是數量還是順序都嚴格規定了(確定性)。
兩種資料型別:無符號數和表
1、無符號數:u1、u2、、u4、u8分別代表1、2、4、8位元組的無符號數。可用來描述數字、索引引用、數量值或UTF-8字串。
2、表:由多個無符號數或者表組成。整個Class檔案可看作是一張表。
3、數量不確定時,必須有一個前置的數量說明。
2、Magic Number和版本號
魔數:標識該檔案是一個Class檔案。
版本號:Minor Version和Major Version,標識該Class檔案的版本(能執行該Class檔案的JVM最低版本)。
Java版本號從45開始,每一個JDK大版本釋出,主版本號加1。
3、常量池
可以理解為Class檔案中的資源倉庫,通常是Class檔案中最佔空間的一部分。被其他部分所引用,常量池元素之間也存在相互引用。就如同搭積木一樣。
(只有)常量池計數從1開始,0留出是為了表示不引用任何常量池元素的情況。
兩大型別常量:
1、字面量Literal:基本型別和字串。
2、符號引用(引用其他常量池元素)Symbolic Reference:
1、類和介面的全限定名;
2、欄位的名稱和描述符;
3、方法的名稱和描述符。
Java編譯過程沒有連結步驟,Class檔案沒有儲存各個方法、欄位在記憶體中的佈局資訊,要依靠虛擬機器載入Class檔案的時候進行動態連結。
虛擬機器執行時,需要從常量池中獲得對應的符號引用,再在類建立或執行時解析、翻譯到具體的記憶體地址之中。
4、訪問標誌
通過標誌位用兩個位元組表示。
5、類索引、父類索引和介面索引集合
這三個索引用於確定類的繼承關係。
類索引、父類索引和介面索引集合按順序排列在訪問標誌之後,其中類索引和父類索引各用一個u2型別的索引值表示,指向常量池中的一個CONSTANT_Class_info元素。
由於可以實現多個介面,因此,要增加一個介面計數器,隨後緊接著介面索引集合。
介面索引同樣用一個u2型別的索引值表示,指向常量池中的一個CONSTANT_Class_info元素。
6、欄位表集合
name_index引用常量池中的元素,為欄位的簡單名。
descriptor_index引用常量池中的元素,為欄位的描述符(包括資料型別)。
描述符用於描述欄位的資料型別,方法的引數列表(包括數量、型別、順序)和返回值。
由於欄位(方法)名已經有單獨的屬性描述了,因此描述符裡不再包括。
描述方法時,按照先引數列表,後返回值的順序。引數列表嚴格按照引數順序放在“()”之內。void 對應的描述符為V。
描述符示例:
F: int i; D: I
F: java.lang.String[][] S; D: [[Ljava/lang/String
M: void inc(); D: ()V
M: double add(double x, double y); D: (DD)D
M: int add(int[] array); D: ([I)I
M: int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex);
D: ([CII[CIII)I
欄位表不會包含繼承而來的欄位,但可能包含Java程式碼中沒有的欄位(如內部類中包含外部類的引用)。
7、方法表集合
方法表的結構和欄位表一樣,只是每個屬性的具體內容有所變化(如訪問欄位變了)。
如果子類方法表不會包含父類方法,除非子類重寫了那個方法。
可能出現程式碼中沒有的方法,如編譯器新增的類構造器<cinit>方法和例項構造器<init>方法。
Java中特徵簽名就是一個方法中各個引數在常量池中的欄位符號引用的集合,返回值不包含在特徵簽名中,因此Java無法通過返回值區分方法過載。
但是在Class檔案格式中,特徵簽名還包括方法返回值和受查異常表,因此只有返回值不同的兩個方法也能在Class檔案中共存。
編譯後的位元組碼去哪兒了?
在方法屬性表集合中的Code屬性裡面。
8、屬性表集合
屬性表都有固定的兩項
1、u2型別的attribute_name_index,指向常量池中CONSTANT_Utf8_info 型常量的索引,代表屬性名。
2、u4型別的attribute_length,表示後面的attribute_info的數量。
1、code屬性-方法表
javac編譯器編譯處理後的位元組碼指令,一個位元組表示一個指令。
並非所有方法都存在Code屬性,抽象方法就沒有。
exception_table:
2、Exceptions屬性-方法表
與Code屬性平級的屬性,不是exception_table !
3、LineNumberTable屬性-Code屬性
描述Java原始碼與位元組碼偏移量的對應關係,用於除錯。
可在編譯時加 -g:none 或 -g:lines 引數關閉。
4、LocalVariableTable屬性-Code屬性
描述棧幀中區域性變數表與Java原始碼中定義的變數之間的關係。
可在編譯時加 -g:none 或 -g:vars 引數關閉。
5、SourceFile屬性-類檔案
記錄Class檔案對應的原始碼檔案的名稱。
可在編譯時加 -g:none 或 -g:source 引數關閉。
6、ConstantValue屬性-欄位表
記錄編譯時常量的值。
7、InnerClasses 屬性
記錄內部類與宿主類的關聯。
8、Deprecated和Synthetic屬性
Deprecated類似於@Deprecated。
Synthetic表示此欄位或方法不是Java原始碼直接生成的。
9、StackMapTable 屬性
用於類載入時的型別檢查。
10、Signiture屬性
用於在擦除法後獲取泛型的資訊。
11、BootstrapMethods屬性
用於儲存invokedynamic指令引用的引導方法限定符。
9、位元組碼指令簡介
Java位元組碼指令和C/C++彙編不太一樣,它沒有暫存器的概念。而是採用了運算元棧來進行資料的傳遞和運算。
同時,一個位元組序列就是一個指令。因此最多隻能有256個指令。因此,不是每種型別資料都有對應的運算指令。。
可以分為:
1、載入和儲存指令
2、運算指令
3、型別轉換指令
4、物件建立與訪問指令
5、運算元棧管理指令
6、控制轉移指令
7、方法呼叫和返回指令
8、異常處理指令
9、同步指令