《Java虛擬機器原理圖解》 1.1、class檔案基本組織結構

五柳-先生發表於2015-05-30
作為Java程式猿,我們知道,我們寫好的.java 原始碼,最後會被Java編譯器編譯成字尾為.class的檔案,該型別的檔案是由位元組組成的檔案,又叫位元組碼檔案。那麼,class位元組碼檔案裡面到底是有什麼呢?它又是怎樣組織的呢?讓我們先來大概瞭解一下他的組成結構吧。


NO1. 魔數(magic)

      所有的由Java編譯器編譯而成的class檔案的前4個位元組都是“0xCAFEBABE”  
      它的作用在於:當JVM在嘗試載入某個檔案到記憶體中來的時候,會首先判斷此class檔案有沒有JVM認為可以接受的“簽名”,即JVM會首先讀取檔案的前4個位元組,判斷該4個位元組是否是“0xCAFEBABE”,如果是,則JVM會認為可以將此檔案當作class檔案來載入並使用。


NO2.版本號(minor_version,major_version)

      隨著Java本身的發展,Java語言特性和JVM虛擬機器也會有相應的更新和增強。目前我們能夠用到的JDK版本如:1.5,1.6,1.7,還有現如今最新的1.8。釋出新版本的目的在於:在原有的版本上增加新特性和相應的JVM虛擬機器的優化。而隨著主版本釋出的次版本,則是修改相應主版本上出現的bug。我們平時只需要關注主版本就可以了。

主版本號和次版本號在class檔案中各佔兩個位元組,副版本號佔用第5、6兩個位元組,而主版本號則佔用第7,8兩個位元組。JDK1.0的主版本號為45,以後的每個新主版本都會在原先版本的基礎上加1。若現在使用的是JDK1.7編譯出來的class檔案,則相應的主版本號應該是51,對應的7,8個位元組的十六進位制的值應該是 0x33。

      一個 JVM例項只能支援特定範圍內的主版本號 (MiMj) 和 0 至特定範圍內 (0 至 m) 的副版本號。假設一個 Class 檔案的格式版本號為 V, 僅當Mi.0 ≤ v ≤ Mj.m成立時,這個 Class 檔案才可以被此 Java 虛擬機器支援。不同版本的 Java 虛擬機器實現支援的版本號也不同,高版本號的 Java 虛擬機器實現可以支援低版本號的 Class 檔案,反之則不成立。

     JVM在載入class檔案的時候,會讀取出主版本號,然後比較這個class檔案的主版本號和JVM本身的版本號,如果JVM本身的版本號 < class檔案的版本號,JVM會認為載入不了這個class檔案,會丟擲我們經常見到的"java.lang.UnsupportedClassVersionError: Bad version number in .class file " Error 錯誤;反之,JVM會認為可以載入此class檔案,繼續載入此class檔案。

     

    小貼士:

1. 有時候我們在執行程式時會丟擲這個Error 錯誤:"java.lang.UnsupportedClassVersionError: Bad version number in .class file"。上面已經揭示了出現這個問題的原因,就是在於當前嘗試載入class檔案的JVM虛擬機器的版本 低於class檔案的版本。解決方法:1.重新使用當前jvm編譯原始碼,然後再執行程式碼;2.將當前JVM虛擬機器更新到class檔案的版本。

2. 怎樣檢視class檔案的版本號?

 可以藉助於文字編輯工具,直接檢視該檔案的7,8個位元組的值,確定class檔案是什麼版本的。

當然快捷的方式使用JDK自帶的javap工具,如當前有Programmer.class 檔案,進入此檔案所在的目錄,然後執行 ”javap -v Programmer“,結果會類似如下所示:

    



NO3.常量池計數器(constant_pool_count)

 常量池是class檔案中非常重要的結構,它描述著整個class檔案的字面量資訊。 常量池是由一組constant_pool結構體陣列組成的,而陣列的大小則由常量池計數器指定。常量池計數器constant_pool_count 的值 =constant_pool表中的成員數+ 1。constant_pool表的索引值只有在大於 0 且小於constant_pool_count時才會被認為是有效的。

NO4.常量池資料區(constant_pool[contstant_pool_count-1])

常量池,constant_pool是一種表結構,它包含 Class 檔案結構及其子結構中引用的所有字串常量、 類或介面名、欄位名和其它常量。 常量池中的每一項都具備相同的格式特徵——第一個位元組作為型別標記用於識別該項是哪種型別的常量,稱為 “tag byte” 。常量池的索引範圍是 1 至constant_pool_count−1。常量池的具體細節我們會稍後討論。


NO6.訪問標誌(access_flags)

       訪問標誌,access_flags 是一種掩碼標誌,用於表示某個類或者介面的訪問許可權及基礎屬性。

      




NO7.類索引(this_class)

       類索引,this_class的值必須是對constant_pool表中專案的一個有效索引值。constant_pool表在這個索引處的項必須為CONSTANT_Class_info 型別常量,表示這個 Class 檔案所定義的類或介面。



NO8.父類索引(super_class)

     父類索引,對於類來說,super_class 的值必須為 0 或者是對constant_pool 表中專案的一個有效索引值。如果它的值不為 0,那 constant_pool 表在這個索引處的項必須為CONSTANT_Class_info 型別常量,表示這個 Class 檔案所定義的類的直接父類。當前類的直接父類,以及它所有間接父類的access_flag 中都不能帶有ACC_FINAL 標記。對於介面來說,它的Class檔案的super_class項的值必須是對constant_pool表中專案的一個有效索引值。constant_pool表在這個索引處的項必須為代表 java.lang.Object CONSTANT_Class_info 型別常量 。如果 Class 檔案的 super_class的值為 0,那這個Class檔案只可能是定義的是java.lang.Object類,只有它是唯一沒有父類的類。



NO9.介面計數器(interfaces_count)

      介面計數器,interfaces_count的值表示當前類或介面的直接父介面數量。



NO10.介面資訊資料區(interfaces[interfaces_count])

      介面表,interfaces[]陣列中的每個成員的值必須是一個對constant_pool表中專案的一個有效索引值, 它的長度為 interfaces_count。每個成員 interfaces[i]  必須為 CONSTANT_Class_info型別常量,其中 0 ≤ i <interfaces_count。在interfaces[]陣列中,成員所表示的介面順序和對應的原始碼中給定的介面順序(從左至右)一樣,即interfaces[0]對應的是原始碼中最左邊的介面。



NO11.欄位計數器(fields_count)

      欄位計數器,fields_count的值表示當前 Class 檔案 fields[]陣列的成員個數。 fields[]陣列中每一項都是一個field_info結構的資料項,它用於表示該類或介面宣告的類欄位或者例項欄位。


NO12.欄位資訊資料區(fields[fields_count])

      欄位表,fields[]陣列中的每個成員都必須是一個fields_info結構的資料項,用於表示當前類或介面中某個欄位的完整描述。 fields[]陣列描述當前類或介面宣告的所有欄位,但不包括從父類或父介面繼承的部分。



NO13.方法計數器(methods_count)

     方法計數器, methods_count的值表示當前Class 檔案 methods[]陣列的成員個數。Methods[]陣列中每一項都是一個 method_info 結構的資料項。


NO14.方法資訊資料區(methods[methods_count])

      方法表,methods[] 陣列中的每個成員都必須是一個 method_info 結構的資料項,用於表示當前類或介面中某個方法的完整描述。如果某個method_info 結構的access_flags 項既沒有設定 ACC_NATIVE 標誌也沒有設定ACC_ABSTRACT 標誌,那麼它所對應的方法體就應當可以被 Java 虛擬機器直接從當前類載入,而不需要引用其它類。 method_info結構可以表示類和介面中定義的所有方法,包括例項方法、類方法、例項初始化方法方法和類或介面初始化方法方法 。methods[]陣列只描述當前類或介面中宣告的方法,不包括從父類或父介面繼承的方法。



NO15.屬性計數器(attributes_count)

     屬性計數器,attributes_count的值表示當前 Class 檔案attributes表的成員個數。attributes表中每一項都是一個attribute_info 結構的資料項。


NO16.屬性資訊資料區(attributes[attributes_count])

     屬性表,attributes 表的每個項的值必須是attribute_info結構。

    在Java 7 規範裡,Class檔案結構中的attributes表的項包括下列定義的屬性: InnerClasses  、 EnclosingMethod 、 Synthetic  、Signature、SourceFile,SourceDebugExtension 、Deprecated、RuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations以及BootstrapMethods屬性。

      對於支援 Class 檔案格式版本號為 49.0 或更高的 Java 虛擬機器實現,必須正確識別並讀取attributes表中的SignatureRuntimeVisibleAnnotationsRuntimeInvisibleAnnotations屬性。對於支援Class檔案格式版本號為 51.0 或更高的 Java 虛擬機器實現,必須正確識別並讀取 attributes表中的BootstrapMethods屬性。Java 7 規範 要求任一 Java 虛擬機器實現可以自動忽略 Class 檔案的 attributes表中的若干 (甚至全部) 它不可識別的屬性項。任何本規範未定義的屬性不能影響Class檔案的語義,只能提供附加的描述資訊 。


根據上述的敘述,我們可以將class的檔案組織結構概括成以下面這個結構體:






參考書目:

Java虛擬機器規範(Java SE 7)中文版(Java_Virtual_Machine_Specification_Java_SE_7)

[深入理解Java虛擬機器:JVM高階特性與最佳實踐].周志明

http://blog.csdn.net/luanlouis/article/details/39892027

相關文章