要想深刻的瞭解jvm,瞭解java編譯後的類文件結構和字節碼是頗有必要的。雖然這部份內容(主要是class文件的數據結構)比較枯燥,可是這也是最基礎的內容,是咱們深刻理解jvm的內存、類的加載等內容的基石。java
class文件是一組以8位字節爲基礎的二進制流,各個數據項目按照順序排列在Class文件中,中間沒有任何分隔符。所以整個class文件中存儲的內容幾乎全是程序運行時的必要數據。當遇到須要佔用8位以上字節空間的數據項時,會按照高位在前的方式分割成若干個8位字節存儲。數組
class文件格式以下:數據結構
類型 | 名稱 | 數量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
class文件只有兩種僞數據結構:無符號數和表。能夠看到每一個表的前面都會有一XX count的,這是一個前置容量計數器,用來記錄對應類型的數量。併發
class文件的數據項不少,這裏不展開一個個地講,主要介紹一些關鍵的。jvm
- 常量池
- 字段表
- 方法表
- 屬性表
常量池相信不少人都聽過,這裏的常量池指的是class文件中的常量池。佈局
常量池中主要存儲兩類類型:字面量和符號引用。this
字面量:字面量指的是java語言層的常亮,如String s="123",那麼這個"123"就是常量。對於基本類型的封裝類型,範圍在-127-128之間的,也是常量。固然了,聲明爲final的值,在整個程序運行過程當中不可變,天然也是常量了。編碼
符號引用:java中的符號引用主要包括如下3類常量:翻譯
java是在虛擬機加載class文件的時候進行動態鏈接的,class文件中不會保存各方法字段的最終內存佈局信息,所以這些字段、方法的符號引用不通過虛擬機運行時轉換的話,沒法獲得真正的內存地址,也就沒法被jvm使用。當虛擬機運行時,須要從常量池得到對應的符號引用,再在類建立或運行時解析,翻譯到具體的內存地址中。調試
常量池中的每一項常量都是一個表,這些表有一個共同的特色,每一個表的第一位都是一個u1類型的標誌位,取值以下:
常量池中數據項類型 | 類型標誌 | 類型描述 |
---|---|---|
CONSTANT_Utf8 | 1 | UTF-8編碼的Unicode字符串 |
CONSTANT_Integer | 3 | int類型字面值 |
CONSTANT_Float | 4 | float類型字面值 |
CONSTANT_Long | 5 | long類型字面值 |
CONSTANT_Double | 6 | double類型字面值 |
CONSTANT_Class | 7 | 對一個類或接口的符號引用 |
CONSTANT_String | 8 | String類型字面值 |
CONSTANT_Fieldref | 9 | 對一個字段的符號引用 |
CONSTANT_Methodref | 10 | 對一個類中聲明的方法的符號引用 |
CONSTANT_InterfaceMethodref | 11 | 對一個接口中聲明的方法的符號引用 |
CONSTANT_NameAndType | 12 | 對一個字段或方法的部分符號引用 |
訪問標誌用來識別類或接口的訪問信息,好比這個Class是類仍是接口,是public仍是private,是否被聲明爲final等。具體標誌位及含義以下:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 是否爲Public類型 |
ACC_FINAL | 0x00 10 | 是否被聲明爲final,只有類能夠設置 |
ACC_SUPER | 0x00 20 | 是否容許使用invokespecial字節碼指令的新語義. |
ACC_INTERFACE | 0x02 00 | 標誌這是一個接口 |
ACC_ABSTRACT | 0x04 00 | 是否爲abstract類型,對於接口或者抽象類來講,次標誌值爲真,其餘類型爲假 |
ACC_SYNTHETIC | 0x10 00 | 標誌這個類並不是由用戶代碼產生 |
ACC_ANNOTATION | 0x20 00 | 標誌這是一個註解 |
ACC_ENUM | 0x40 00 | 標誌這是一個枚舉 |
咱們都知道,java中是單繼承多實現的,除了Object類以外每一個類都有父類,所以它們是惟一的,而一個類能夠實現多個接口,所以接口是不惟一的,用集合表示。類索引和父類索引都是用一個u2類型數據來表示的,而接口索引集合則是一組u2類型的數據表示。
類索引、父類索引和接口索引集合都按順序排在訪問標誌的後面。類索引和父類索引u2類型的索引各指向一個類型爲CONSTANT_Class_info的類描述符常量,用來描述具體的類。對於接口索引第一項u2則爲接口索引計數器,用來記錄實現了多少個接口,若是爲0則後面再也不佔用任何字節。
字段表用於聲明類或接口中聲明的變量。字段包括類變量和實例變量。在java中一個字段是如何描述的,舉個的例子
public static final String num="13234";
能夠看出來,首先是訪問範圍,是公有的仍是私有的,或者受保護的,這個信息決定了字段是否堆特定範圍的類可見。
其次是一些關鍵字修飾的描述信息,是實例變量仍是類變量,是否可變,併發可見性,是否可被序列化等,這些關鍵字包括static、final 、volatile、transient等。
在後面即是字段的數據類型(基本數據類型、數組、對象)和名稱。
上述的這些修飾符都是用布爾值來描述的,而數據類型和名稱都是不肯定的,一般引用常量池的常量來描述。
字段表結構以下:
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
字段修飾符在access_flag下,access_flag的內容以下:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否爲public |
ACC_PRIVATE | 0x0002 | 字段是否爲private |
ACC_PROTECTED | 0x0004 | 字段是否爲protected |
ACC_STATIC | 0x0008 | 字段是否爲static |
ACC_FINAL | 0x0010 | 字段是否爲final |
ACC_VOLATILE | 0x0040 | 字段是否爲volatile |
ACC_TRANSTENT | 0x0080 | 字段是否爲transient |
ACC_SYNCHETIC | 0x1000 | 字段是否爲由編譯器自動產生 |
ACC_ENUM | 0x4000 | 字段是否爲enum |
name_index表示的是字段的簡單名稱,像上面的簡單名稱就是「num」,descriptor_index 表示字段和方法的描述符。
描述符號的做用是用來描述字段的數據類型、方法的參數列表(數量、類型及順序)和返回值。根據描述符的規則:基本數據類型以及表明無返回值的void類型都用一個大寫的字符來表示,而對象類型則用字符L加對象的全限定名來描述,詳情以下:
標誌符 | 含義 |
---|---|
B | 基本數據類型byte |
C | 基本數據類型char |
D | 基本數據類型double |
F | 基本數據類型float |
I | 基本數據類型int |
J | 基本數據類型long |
S | 基本數據類型short |
Z | 基本數據類型boolean |
V | 基本數據類型void |
L | 對象類型 |
對於數組類型,每個維度用一個前置的「[」字符來描述,如定義個int[][]類型的二維數組,記錄爲:"[[I"。
用描述符來描述方法時,按照先參數列表後返回值的順序描述。參數裂變按照參數順序放在「()」內,如方法void login()描述符爲「()V」,方法java.lang.String toString()的描述符爲「()Ljava.lang.String」。
Class文件存儲格式中對方法和字段的描述徹底一致,方法表的字段結構和字段表同樣,包括訪問標誌、名稱索引、描述符索引、屬性表集合幾項。這些數據的含義很是相似,在訪問標誌和屬性表集合有所區別。
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
voliate和transient不能修飾方法,因此訪問標誌沒有了ACC_VOLATILE標誌和ACC_TRANSIENT標誌。一樣,一些方法的關鍵字如:synchronized、native、strictfp、abstract等能夠修飾方法,其標誌位取值以下:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否爲public |
ACC_PRIVATE | 0x00 02 | 方法是否爲private |
ACC_PROTECTED | 0x00 04 | 方法是否爲protected |
ACC_STATIC | 0x00 08 | 方法是否爲static |
ACC_FINAL | 0x00 10 | 方法是否爲final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否爲synchronized |
ACC_BRIDGE | 0x00 40 | 方法是不是有編譯器產生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受參數 |
ACC_NATIVE | 0x01 00 | 方法是否爲native |
ACC_ABSTRACT | 0x04 00 | 方法是否爲abstract |
ACC_STRICTFP | 0x08 00 | 方法是否爲strictfp |
ACC_SYNTHETIC | 0x10 00 | 方法是不是有編譯器自動產生的 |
方法的定義能夠經過訪問標誌、名稱和描述符索引來表述清除,那麼方法中的代碼又在哪裏呢?咱們以前提到了屬性表集合,方法裏的java代碼通過編譯器編譯成字節碼指令後,存放在方法的屬性表集合裏一個名爲「Code」的屬性裏。
咱們從上面不止一次的看到了屬性表集合,不論是Class文件、字段表仍是方法表中,都有屬性表集合(attribute_info)這一項,用於描述某些特定場景的專有信息。
與class文件其它數據項目嚴格要求順序長度不一樣,屬性表集合限制相對比較寬鬆,不要求各個屬性表具備嚴格順序,只要不與已有屬性名重複,任何人實現的編譯器都可向屬性表寫入本身的屬性,jvm運行時會自動忽略掉不認識的屬性。
java7中定義的屬性以下表:
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量池 |
Deprecated | 類,方法,字段表 | 被聲明爲deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類爲局部類或者匿名類是才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法 |
InnerClass | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關係 |
LocalVariableTable | Code屬性 | 方法的局部便狼描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性,供新的類型檢查檢驗器檢查和處理目標方法的局部變量和操做數有所須要的類是否匹配 |
Signature | 類,方法表,字段表 | 用於支持泛型狀況下的方法簽名 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | 用於存儲額外的調試信息 |
Synthetic | 類,方法表,字段表 | 標誌方法或字段爲編譯器自動生成的 |
LocalVariableTypeTable | 類 | 使用特徵簽名代替描述符,是爲了引入泛型語法以後能描述泛型參數化類型而添加 |
RuntimeVisibleAnnotations | 類,方法表,字段表 | 爲動態註解提供支持 |
RuntimeInvisibleAnnotations | 表,方法表,字段表 | 用於指明哪些註解是運行時不可見的 |
RuntimeVisibleParameterAnnotation | 方法表 | 做用與RuntimeVisibleAnnotations屬性相似,只不過做用對象爲方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | 做用與RuntimeInvisibleAnnotations屬性相似,做用對象哪一個爲方法參數 |
AnnotationDefault | 方法表 | 用於記錄註解類元素的默認值 |
BootstrapMethods | 類文件 | 用於保存invokeddynamic指令引用的引導方式限定符 |