1、魔數
我們可以利用editplus,以16進制的編碼格式來查看class文件的結構,具體操作方法爲在editplus的工具欄中點擊Edit,下拉選擇Hex Viewer即可。
如圖所示,前四(4個bit位*8個字母=32,32/8=4字節)個字節爲0xCAFEBABE,這就是class文件的魔數。
虛擬機藉助魔數,用來識別.class 文件,虛擬機在加載類文件之前會先檢查魔數,如果不是 0xCAFEBABE 則拒絕加載該文件。
關於class文件魔數的由來,可以參考這篇文章class文件魔數CAFEBABE的由來
2、版本號
版本號分爲副版本號(minor version)與主版本號(major version),緊隨魔數之後。
可以看到主版本號爲52(3*16+4),52對應的java版本爲java8,規律就是java版本=主版本號-44。例如主版本號50對應的java版本爲java6。
如果java6的虛擬機去加載一個java8編譯的類,則虛擬機直接會拋出java.lang.UnsupportedClassVersionError。
我們使用javap -v,也可以直接看到class文件的主副版本號:
3、常量池
常量池緊隨着版本號,是class文件中最爲複雜的部分。
當執行一個java方法時,需要將操作數入棧,這個時候如果操作數很小,那麼直接內嵌到字節碼中。如果是比較大的數字或者是字符串時,就不再會內嵌到字節碼中,而是存到常量池中。當將這些操作數入棧時,字節指令後面會跟着一個指向常量池的一個索引。
比如這個方法:
public void print() {
System.out.println(1);
System.out.println("abcd");
}
對應字節碼爲:
當然,常量池不僅僅存儲字符串類型,完整的常量類型,如下表所示:
類型 |
說明 |
CONSTANT_Utf8_info |
表示utf-8編碼的字符串常量 |
CONSTANT_Integer_info |
表示int常量 |
CONSTANT_Float_info |
表示float常量 |
CONSTANT_Long_info |
表示long常量 |
CONSTANT_Double_info |
表示double常量 |
CONSTANT_Class_info |
表示類或接口的完全限定名 |
CONSTANT_String_info |
表示java.lang.String類型的字符串 |
CONSTANT_Fieldref_info |
表示字段的符號引用 |
CONSTANT_Methodref_info |
表示方法的符號引用 |
CONSTANT_InterfaceMethodref_info |
表示實現的接口方法的符號引用 |
CONSTANT_NameAndType_info |
表示字段或方法的名稱以及類型 |
CONSTANT_MethodHandle_info |
表示方法句柄 |
CONSTANT_MethodType_info |
表示方法類型 |
CONSTANT_InvokeDynamic_info |
表示動態調用 |
符號引用可以這麼去理解:符號引用是一個具有定位意義的字符串,在類加載過程中,連接的子過程解析階段時,虛擬機會將符號引用解析爲直接引用。關於類加載機制,可以先參考我的另外一篇文章類的奇幻漂流——類加載機制探祕。
就以我們最經常用到的System.out.println()方法爲例,out是System類中的一個PrintStream類型的引用變量,println則是PrintStream類中的一個方法,那麼out字段的符號引用與println方法的符號引用是什麼樣子的呢?
以下面的代碼爲例:
package com.yang.testMethod;
public class Main {
public static void main(String[] args) {
System.out.println(1);
}
}
常量池如下:
可以看得出來,out字段的Fieldref=Class+NameAndType,即字段的符號引用=所屬類的符號引用+字段的描述符。
println方法的符號引用也是同樣的組成方式,但方法的NameAndType包含參數類型描述符以及返回值類型描述符。
描述符又是怎樣組織的,可以先看字段表中的字段描述符以及方法表中的方法描述符。
MethodHandle、MethodType與InvokeDynamic是爲了支持動態語言調用,在1.7之後才加入的,這裏不做討論。不過這裏的invokeDynamic很有意思,會另外篇幅進行介紹。
4、類訪問標記
類訪問標記緊隨在常量池之後,佔兩個字節,一共16位,目前只使用了其中8位。
虛擬機在編譯某個類時,會解析出這個類的特性,將其設置到類訪問標記上,即將特定的bit位置1,表示該類擁有這個bit位上代表的標記。
8種標記如下表所示,例如編譯一個public類時,該類的訪問標記上會有ACC_PUBLIC與ACC_SUPER。
標記名稱 |
說明 |
ACC_PUBLIC |
類或接口的訪問權限爲public |
ACC_FINAL |
類被final修飾 |
ACC_SUPER |
類 |
ACC_INTERFACE |
接口 |
ACC_ABSTRACT |
抽象類或接口 |
ACC_SYNTHETIC |
編譯器自動生成,不是用戶對代碼編譯生成 |
ACC_ANNOTATION |
註解 |
ACC_ENUM |
枚舉 |
例如,有這樣的一個java文件:
package com.yang.testFlag;
public interface Main {
}
使用javac Main.java,接着javap -v Main之後,得到該接口的訪問標記爲:
接口是一種特殊的抽象類,所有的變量都爲public static final類型,所有的方法都是抽象方法。(當然除了靜態方法與默認方法)。更多關於抽象類與接口的特徵與區別,可以先參考我的另外一篇文章抽象類和接口的聯繫與區別。
因此,一個public類型的接口,它的訪問標記有3個,分別爲ACC_PUBLIC、ACC_INTERFACE與ACC_ABSTRACT。