魔數(shù)與Class文件的版本
- 魔數(shù)(Magic Number)
- 每個Class文件的頭4個字節(jié)
- 唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件
- 值為:0xCAFEBABE
-
次版本號
- 第5和第6個字節(jié)
-
主版本號
- 第7和第8個字節(jié)
Java的版本號是從45開始的厢洞,JDK 1.1之后的每個JDK大版本發(fā)布主版本號向上加1(JDK 1.0~1.1使用了45.0~45.3的版本號),高版本的JDK能向下兼容以前版本的Class文件典奉,但不能運行以后版本的Class文件躺翻,即使文件格式并未發(fā)生任何變化,虛擬機也必須拒絕執(zhí)行超過其版本號的Class文件卫玖。
常量池
緊接著主次版本號之后的是常量池入口公你,常量池可以理解為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而不是0開始的。
-
第0項常量空出來是有特殊考慮的税肪,這樣做的目的在于滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達(dá)“不引用任何一個常量池項目”的含義溉躲,這種情況就可以把索引值置為0來表示。
圖2 常量池入口數(shù)據(jù)u2益兄,0x16表示有21項常量(1~21) Class文件結(jié)構(gòu)中只有常量池的容量計數(shù)是從1開始锻梳,對于其他集合類型,包括接口索引集合净捅、 字段表集合疑枯、 方法表集合等的容量計數(shù)都與一般習(xí)慣相同,是從0開始的蛔六。
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)
- 字面量
- 比較接近于Java語言層面的常量概念神汹,如文本字符串、 聲明為final的常量值等古今。
- 符號引用
- 屬于編譯原理方面的概念,包括了下面三類常量
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符
- 屬于編譯原理方面的概念,包括了下面三類常量
常量池中每一項常量都是一個表滔以,在JDK 1.7時共有14種結(jié)構(gòu)各不相同的表結(jié)構(gòu)數(shù)據(jù)捉腥,表開始的第一位是一個u1類型的標(biāo)志位,代表當(dāng)前這個常量屬于哪種常量類型你画。
UTF-8縮略編碼與普通UTF-8編碼的區(qū)別:從'\u0001'到'\u007f'之間的字符(相當(dāng)于1~127的ASCII碼)的縮略編碼使用一個字節(jié)表示抵碟,從'\u0080'到'\u07ff'之間的所有字符的縮略編碼用兩個字節(jié)表示從'\u0800'到'\uffff'之間的所有字符的縮略編碼就按照普通UTF-8編碼規(guī)則使用三個字節(jié)表示。
訪問標(biāo)志
在常量池結(jié)束之后坏匪,緊接著的兩個字節(jié)代表訪問標(biāo)志(access_flags)拟逮,這個標(biāo)志用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口适滓;是否定義為public類型敦迄;是否定義為abstract類型;如果是類的話凭迹,是否被聲明為final等罚屋。
類索引、 父類索引與接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù)嗅绸,而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合,Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。
- 類索引
- 用于確定這個類的全限定名
- 父類索引
- 用于確定這個類的父類的全限定名
- 由于Java語言不允許多重繼承渔欢,所以父類索引只有一個,除了java.lang.Object之外羹铅,所有的Java類都有父類,因此除了java.lang.Object外愉昆,所有Java類的父類索引都不為0
- 接口索引集合
- 用來描述這個類實現(xiàn)了哪些接口
- 被實現(xiàn)的接口將按implements語句(如果這個類本身是一個接口职员,則應(yīng)當(dāng)是extends語句)后的接口順序從左到右排列在接口索引集合中。
- 入口的第一項——u2類型的數(shù)據(jù)為接口計數(shù)器(interfaces_count)撼唾,表示索引表的容量廉邑。
類索引、 父類索引和接口索引集合都按順序排列在訪問標(biāo)志之后
字段表集合
用于描述接口或者類中聲明的變量倒谷,包括類級變量以及實例級變量蛛蒙,但不包括在方法內(nèi)部聲明的局部變量。
- access_flags:與類中的access_flags項目是非常類似的渤愁,都是一個u2的數(shù)據(jù)類型
- name_index:字段的簡單名稱
- descriptor_index: 字段和方法的描述符
描述符
用來描述字段的數(shù)據(jù)類型牵祟、 方法的參數(shù)列表(包括數(shù)量、 類型以及順序)和返回值
- 基本數(shù)據(jù)類型(byte抖格、 char诺苹、 double、 float雹拄、 int收奔、 long、 short滓玖、 boolean)以及代表無返回值的void類型都用一個大寫字符來表示坪哄,而對象類型則用字符L加對象的全限定名來表示
- 對于數(shù)組類型,每一維度將使用一個前置的“[”字符來描述
- 如一個定義為“java.lang.String[][]”類型的二維數(shù)組势篡,將被記錄為:“[[Ljava/lang/String翩肌;”,一個整型數(shù)組“int[]”將被記錄為“[I”禁悠。
用描述符來描述方法時念祭,按照先參數(shù)列表,后返回值的順序描述碍侦。參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號“()”之內(nèi)粱坤。 如方法void inc()的描述符為“()V”,方法java.lang.String toString()的描述符為“()Ljava/lang/String瓷产;”比规,方法intindexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,inttargetCount,int fromIndex)的描述符為“([CII[CIII)I”。
字段表都包含的固定數(shù)據(jù)項目到descriptor_index為止就結(jié)束了拦英,不過在descriptor_index之后跟隨著一個屬性表集合用于存儲一些額外的信息蜒什,字段都可以在屬性表中描述零至多項的額外信息。
字段表集合中不會列出從超類或者父接口中繼承而來的字段疤估,但有可能列出原本Java代碼之中不存在的字段灾常,譬如在內(nèi)部類中為了保持對外部類的訪問性霎冯,會自動添加指向外部類實例的字段。
方法表集合
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用了完全一致的方式钞瀑,方法表的結(jié)構(gòu)如同字段表一樣沈撞,依次包括了訪問標(biāo)志(access_flags)、 名稱索引(name_index)雕什、 描述符索引(descriptor_index)缠俺、 屬性表集合(attributes)幾項
方法里的Java代碼,經(jīng)過編譯器編譯成字節(jié)碼指令后贷岸,存放在方法屬性表集合中一個名為“Code”的屬性里面
如果父類方法在子類中沒有被重寫(Override)壹士,方法表集合中就不會出現(xiàn)來自父類的方法信息。但同樣的偿警,有可能會出現(xiàn)由編譯器自動添加的方法躏救,最典型的便是類構(gòu)造器“<clinit>”方法和實例構(gòu)造器“<init>”方法。
在Java語言中螟蒸,要重載(Overload)一個方法盒使,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名
- 特征簽名就是一個方法中各個參數(shù)在常量池中的字段符號引用的集合
- 因為返回值不會包含在特征簽名中七嫌,因此Java語言里面是無法僅僅依靠返回值的不同來對一個已有方法進(jìn)行重載的少办。
屬性表集合
在Class文件、 字段表诵原、 方法表都可以攜帶自己的屬性表集合英妓,以用于描述某些場景專有的信息。
不要求各個屬性表具有嚴(yán)格順序
-
Code屬性
Java程序方法體中的代碼經(jīng)過Javac編譯器處理后皮假,最終變?yōu)樽止?jié)碼指令存儲在Code屬性內(nèi)
-
Code屬性出現(xiàn)在方法表的屬性集合之中,但并非所有的方法表都必須存在這個屬性骂维,譬如接口或者抽象類中的方法就不存在Code屬性
圖7 Code屬性表的結(jié)構(gòu) attribute_name_index:指向CONSTANT_Utf8_info型常量的索引惹资,常量值固定為“Code”,它代表了該屬性的屬性名稱
attribute_length:指示了屬性值的長度航闺,由于屬性名稱索引與屬性長度一共為6字節(jié)褪测,所以屬性值的長度固定為整個屬性表長度減去6個字節(jié)
max_stack:代表了操作數(shù)棧(Operand Stacks)深度的最大值。 在方法執(zhí)行的任意時刻潦刃,操作數(shù)棧都不會超過這個深度侮措。 虛擬機運行的時候需要根據(jù)這個值來分配棧幀(StackFrame)中的操作棧深度。
max_locals:代表了局部變量表所需的存儲空間乖杠。 在這里分扎,max_locals的單位是Slot,Slot是虛擬機為局部變量分配內(nèi)存所使用的最小單位。 對于byte胧洒、 char畏吓、 float墨状、 int、 short菲饼、 boolean和returnAddress等長度不超過32位的數(shù)據(jù)類型肾砂,每個局部變量占用1個Slot,而double和long這兩種64位的數(shù)據(jù)類型則需要兩個Slot來存放宏悦。 方法參數(shù)(包括實例方法中的隱藏參數(shù)“this”)镐确、 顯式異常處理器的參數(shù)(Exception Handler Parameter,就是try-catch語句中catch塊所定義的異常)饼煞、 方法體中定義的局部變量都需要使用局部變量表來存放源葫。 另外,并不是在方法中用到了多少個局部變量派哲,就把這些局部變量所占Slot之和作為max_locals的值臼氨,原因是局部變量表中的Slot可以重用,當(dāng)代碼執(zhí)行超出一個局部變量的作用域時芭届,這個局部變量所占的Slot可以被其他局部變量所使用储矩,Javac編譯器會根據(jù)變量的作用域來分配Slot給各個變量使用,然后計算出max_locals的大小褂乍。
code_length:字節(jié)碼長度
-
code:用來存儲Java源程序編譯后生成的字節(jié)碼指令
圖8 方法的字節(jié)碼指令
Args_size為1持隧,表示Javac編譯器編譯的時候把對this關(guān)鍵字的訪問轉(zhuǎn)變?yōu)閷σ粋€普通方法參數(shù)的訪問,然后在虛擬機調(diào)用實例方法時自動傳入此參數(shù)逃片。因此在實例方法的局部變量表中至少會存在一個指向當(dāng)前對象實例的局部變量屡拨,局部變量表中也會預(yù)留出第一個Slot位來存放對象實例的引用,方法參數(shù)值從1開始計算褥实。 這個處理只對實例方法有效呀狼,如果方法聲明為static,那Args_size就不會等于1而是等于0了损离。
在字節(jié)碼指令之后的是這個方法的顯式異常處理表(下文簡稱異常表)集合哥艇,異常表對于Code屬性來說并不是必須存在的
Exceptions屬性
是在方法表中與Code屬性平級的一項屬性,作用是列舉出方法中可能拋出的受查異常LineNumberTable屬性
用于描述Java源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系。LocalVariableTable屬性
用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系SourceFile屬性
用于記錄生成這個Class文件的源碼文件名稱僻澎。ConstantValue屬性
通知虛擬機自動為靜態(tài)變量賦值貌踏。只有被static關(guān)鍵字修飾的變量(類變量)才可以使用這項屬性。
對于非static類型的變量(也就是實例變量)的賦值是在實例構(gòu)造器<init>方法中進(jìn)行的窟勃;而對于類變量祖乳,則有兩種方式可以選擇:在類構(gòu)造器<clinit>方法中或者使用ConstantValue屬性。
目前Sun Javac編譯器的選擇是:如果同時使用final和static來修飾一個變量(按照習(xí)慣秉氧,這里稱“常量”更貼切)眷昆,并且這個變量的數(shù)據(jù)類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進(jìn)行初始化,如果這個變量沒有被final修飾隙赁,或者并非基本類型及字符串垦藏,則將會選擇在<clinit>方法中進(jìn)行初始化。InnerClasses屬性
用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)伞访。Deprecated及Synthetic屬性
Deprecated屬性用于表示某個類掂骏、 字段或者方法,已經(jīng)被程序作者定為不再推薦使用厚掷,它可以通過在代碼中使用@deprecated注釋進(jìn)行設(shè)置弟灼。
Synthetic屬性代表此字段或者方法并不是由Java源碼直接產(chǎn)生的,而是由編譯器自行添加的-
StackMapTable屬性
會在虛擬機類加載的字節(jié)碼驗證階段被新類型檢查驗證器(Type Checker)使用- 包含零至多個棧映射幀(Stack Map Frames)冒黑,每個棧映射幀都顯式或隱式地代表了一個字節(jié)碼偏移量田绑,用于表示該執(zhí)行到該字節(jié)碼時局部變量表和操作數(shù)棧的驗證類型。
Signature屬性
任何類抡爹、 接口掩驱、 初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息冬竟。BootstrapMethods屬性
用于保存invokedynamic指令引用的引導(dǎo)方法限定符欧穴。