? ? Write Once Run Anywhere。各種不同平臺的虛擬機與所有平臺都統(tǒng)一使用的程序存儲格式---字節(jié)碼(ByteCode)是構(gòu)成平臺無關(guān)性的基石。
? ? 語言無關(guān)性也越來越被開發(fā)者重視,實現(xiàn)語言無關(guān)性的基礎(chǔ)仍然是虛擬機和字節(jié)碼存儲格式。
一、Class類文件的結(jié)構(gòu)
? ? ? Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在。當遇到需要占用8位字節(jié)以上的空間的數(shù)據(jù)項時,則會按照高位在前的方式分割成若干個0位字節(jié)進行存儲。Class文件中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
? ? ? 無符號數(shù)屬于基本數(shù)據(jù)類型,以u1,u2,u4,u8來分別代表1個字節(jié)、2個字節(jié)、4個字節(jié)、8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串。
? ? ? 表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的符合數(shù)據(jù)類型,所有表都習慣性地以“_info”結(jié)尾。表用于描述有層次關(guān)系的符合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表。
? ? ? 表6-1

1、魔數(shù)與Class文件的版本
? ? ? 每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個文件是否為一個虛擬機接受的Class文件。緊接著魔數(shù)的4個字節(jié)存儲的是Class文件的版本號:第5和第6個字節(jié)是次版本號(Minor Version),第7和第8個字節(jié)是主版本號(Major Version)。Java 的版本號是從45開始的,高版本的JDK能向下兼容以前版本的Class文件,不能執(zhí)行超過其版本號的Class文件。表中列出了從JDK1.1到JDK1.7,主流JDK版本編譯器輸出的默認和可支持的Class文件版本號。
? ? 表6-2 class文件版本號

2、常量池
? ? ? 緊接著主次版本號之后的是常量池入口,常量池可以理解為Class文件中的資源倉庫,它是Class文件結(jié)構(gòu)中與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項目之一,同時它還是在Class文件中第一個出現(xiàn)的表類型數(shù)據(jù)項目。
? ? ? 由于常量池中常量的數(shù)量不是固定的,所以在常量池的入口需要放置一項u2類型的數(shù)據(jù),代表常量池容量計數(shù)值(constant_pool_count)。這個容量計數(shù)是從1開始的。
? ? ? ? 常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念,如文本字符串,聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,包括了下面三類常量:類和接口的全限定名(Full Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
? ? ? 常量池中每一項常量都是一個表,總共有14中標,都有一個共同的特點,就是開始的第一位是一個u1類型的標志位(tag),代表當前這個常量屬于哪種常量類型。
? ? ? 表6-3 常量池的項目類型

? ? ? ? 之所以說常量池是最繁瑣的的數(shù)據(jù),是因為這14種常量類型各自均有自己的結(jié)構(gòu)。CONSTANT_Class_info類型的常量代表一個類或者接口的符號引用。表6-4

? ? ? ? tag是標志位,它用于區(qū)分常量類型;name_index是一個索引值,它指向常量池中一個CONSTANT_Uft8_info類型常量,此常量代表了這個類(或者接口)的全限定名。表6-5

? ? ? ? ? length值說明了這個UTF-8編碼的字符串長度是多少字節(jié),它后面緊跟著的長度為length字節(jié)的連續(xù)數(shù)據(jù)是一個使用UTF-8縮略編碼表示的字符串。由于Class文件中方法、字段等都需要引用CONSTANT_Uft8_info型常量來描述名稱,所以CONSTANT_Uft8_info型常量的最大長度也就是Java中方法、字段名的最大長度。而length最大值為65535.所以Java程序中不能定義超過64KB英文字符的變量或方法名,否則將會無法編譯。表6-6


3、訪問標志
? ? ? 在常量池結(jié)束之后,緊接著的兩個字節(jié)代表訪問標志(access_flags),這個標志用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。具體見下表。
? ? ? 表6-7 訪問標志

? ? ? access_flag中一共有16個標志位可以使用,當前只定義了其中8個,沒有使用到的標志要求一律為0。
4、類索引、父類索引與接口索引集合
? ? 類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù),而接口索引集合(interface)是一組u2類型的數(shù)據(jù)的集合,Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。類索引用于確定這個類的全限定名,父類索引用于確定這個類的父類的全限定名。
? ? 類索引、父類索引和接口索引集合都按順序排列在訪問標志之后后,類索引和父類索引用兩個u2類型的索引值表示,他們各自指向一個類型為CONSTANT_Class_info的描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
? ? ? 對于接口索引集合,入口的第一項----u2類型的數(shù)據(jù)為接口計數(shù)器(interfaces_count),表示索引表的容量。如果該類沒有實現(xiàn)任何接口,則該計數(shù)器值為0,后面接口的索引表不再占用任何字節(jié)。
5、字段表集合
? ? ? 字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
? ? ? 表6-8 字段表結(jié)構(gòu)

? ? ? 字段修飾符放在access_flags項目中,它與類中的access_flags項目是非常類似的,都是一個u2的數(shù)據(jù)類型。
? ? ? ? 表6-9 字段訪問標志

? ? ? ? 在實際情況中,ACC_PUBLIC、ACC_PRIVATE 、ACC_PROTECTED三個標志最多只能選擇其中一個,ACC_FINAL 、ACC_VOLATILE不能同時選擇。接口之中的字段必須有ACC_PUBLIC 、ACC_STATIC、 ACC_STATIC、 ACC_FINAL標志,這些是由Java語言規(guī)則決定的。
? ? ? ? 緊跟access_flags標志的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表著字段的簡單名稱以及字段和方法的描述符。
? ? ? ? 字段和方法的描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double 、float、 int、 long、 short、 boolean)以及代表無返回值的void類型都用一個大寫字母來表示,而對象類型則用字符L加對象的全限定名來表示。表6-10。

6、方法表集合
? ? ? 方法表結(jié)構(gòu)如同字段表一樣,依次包括了訪問標志(access_flag)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attribute)幾項,這些數(shù)據(jù)項目的含義也類似,僅在訪問標志和屬性表集合的可選項中有所區(qū)別。
? ? ? 表6-11 方法表結(jié)構(gòu)

? ? ? ? 因為volatile關(guān)鍵字和transient關(guān)鍵字不能修飾方法,所以方法表的訪問標志中沒有了ACC_VOLATILE標志和ACC_TRANSIENT標志。與之相對的,synchronized、native、strictfp和abstract關(guān)鍵字可以修飾方法,所以方法表的訪問標志中增加了ACC_SYNCHRONIXED 、ACC_NATIVE、 ACC_STRICTFP和ACC_ABSTRACT標志。對于方法表,所有標志位及其取值見表6-12。

7、屬性表集合
? ? ? 屬性表(attribute_info),在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用于描述某些場景專有的信息。表6-13


對于每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則是完全自由定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數(shù)即可。
? 表6-14 屬性表結(jié)構(gòu)

? Code屬性
? ? Java程序方法體中的代碼經(jīng)過Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲在COde屬性內(nèi)。Code屬性出現(xiàn)在方法表的屬性集合中,但并非所有方法表都必須存在這個屬性,譬如接口或者抽象類中的方法就不存在Code屬性,如果方法表有Code屬性存在,那么它的結(jié)構(gòu)如表6-15.
? ? 表6-15 Code屬性表結(jié)構(gòu)

? ? attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定位“Code”,它代表了該屬性的屬性名稱,attribute_length指示了屬性值的長度,由于屬性名稱索引與屬性長度一共為6個字節(jié),所以屬性值的長度固定位整個屬性表長度減去6個字節(jié)。
? ? max_stack代表了操作數(shù)棧(Operand Stack)深度的最大值。在方法執(zhí)行的任意時刻,操作數(shù)棧都不會超過這個深度。虛擬機運行的時候需要根據(jù)這個值來分配棧幀(Stack Frame)中的操作棧深度。
? ? max_locals代表了局部變量表所需要的存儲空間。在這里,max_locals的單位是Slot,Slot是虛擬機為局部變量分配內(nèi)存所使用的最小單位。
? ? code_length和code用來存儲Java源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長度,code是用于存儲字節(jié)碼指令的一系列字節(jié)流。
? ? Code屬性是class文件中最重要的一個屬性。
? ? Exceptions屬性,作用是列舉出方法中可能拋出的受檢查異常(Checked Exception),也就是方法描述時在throws關(guān)鍵字后面列舉異常。LineNumberTable屬性,用于描述Java源碼行號與字節(jié)碼行號之間的對應關(guān)系。LocalVariableTable屬性,用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系。SourceFile屬性,用于記錄生成這個Class文件的源碼文件名稱。ConstantValue屬性的作用是通知虛擬機自動為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的變量才可以使用這項屬性。InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。Deprecated以及Synthetic兩個屬性都屬于標志類型的布爾屬性,只存在有和沒有的區(qū)別,沒有屬性值的概念。StackMapTable屬性,它是一個復雜的變長屬性,位于Code屬性的屬性表中。Signature屬性,它是一個可選的定長屬性,可以出現(xiàn)在類、字段表和方法表結(jié)構(gòu)的屬性表中。BootstrapMethods屬性,它是一個復雜的變長屬性,位于類文件的屬性表中。
? ? ? 以上內(nèi)容就是java字節(jié)碼文件的結(jié)構(gòu),java字節(jié)碼還有字節(jié)碼指令,這里就不多贅述了。有興趣可看《深入Java虛擬機-JVM高級特性與最佳實踐》---周志華這本書,挺不錯的。
本文來自于《深入Java虛擬機-JVM高級特性與最佳實踐》---周志明。如果侵權(quán),請聯(lián)系作者刪除。