本文是《深入理解Java虛擬機》中類文件結(jié)構(gòu)一章的讀書筆記。另外粥鞋,推薦閱讀Java字節(jié)碼結(jié)構(gòu)解析來加深理解缘挽。
Class文件組成內(nèi)容
class文件是一組以8位字節(jié)為基礎的二進制流,其與Java虛擬機指令集和符號表以及若干其他輔助信息相對應呻粹。
該設計有如下優(yōu)點:
- 平臺無關(guān)性壕曼,class文件可以運行在任意平臺,無需考慮各個平臺機器指令集不同的問題
- 語言無關(guān)性尚猿,不論何種語言窝稿,只要生成的class文件格式符合JVM虛擬機規(guī)范即可
注:如果遇到8位字節(jié)以上空間的數(shù)據(jù),則會按照高位在前的方式分割成若干個8位字節(jié)進行存儲(Big-Endian凿掂,具體是指最高位字節(jié)在地址最低位纹蝴、最低位字節(jié)在地址最高位的順序來存儲數(shù)據(jù),它是SPARC踪少、PowerPC等處理器的默認多字節(jié)順序塘安,而x86等處理器則是使用了相反的 Little-Endian 順序來存儲數(shù)據(jù))
Class文件數(shù)據(jù)結(jié)構(gòu)
class文件采用了類似C語言結(jié)構(gòu)體的形式來存儲數(shù)據(jù),主要有以下幾個特點:
- 由無符號數(shù)和表兩種數(shù)據(jù)結(jié)構(gòu)組成
- 集合援奢,用來描述同一類型但數(shù)量不定的多個數(shù)據(jù)兼犯,格式為 容量計數(shù)器 + 數(shù)據(jù)集合
- 沒有任何分割符號(每個字節(jié)代表的含義,長度集漾,先后順序都不允許改變)
無符號數(shù)
定義:class文件基本的數(shù)據(jù)類型切黔,用來描述數(shù)字、索引引用具篇、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值纬霞。
表現(xiàn)形式:以u1、u2驱显、u4诗芜、u8來分別代表1個字節(jié)、2個字節(jié)埃疫、4個字節(jié)伏恐、8個字節(jié)的無符號數(shù)。
表
組成:由無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復合數(shù)據(jù)類型
特征:以_info 結(jié)尾
功能:用于描述有層次關(guān)系復合結(jié)構(gòu)的數(shù)據(jù)
整個class文件本質(zhì)上就是一張表
Class文件數(shù)據(jù)項
按照class文件中字節(jié)碼的順序來介紹數(shù)據(jù)項栓霜。
魔數(shù)
class文件的頭4個字節(jié)翠桦。
功能:驗證該文件是否能夠被虛擬機接受
擴展名可以被修改
主次版本號
魔數(shù)后4個字節(jié),第5個和第6個字節(jié)是次版本號(Minor Version)叙淌,第7個和第8個字節(jié)是主版本號(Major Version)秤掌。
Java版本號從45開始,每個大版本發(fā)布版本號 +1
虛擬機拒絕超過其版本號的Class文件
常量池
可以說常量池是class文件的資源倉庫鹰霍,主要存放兩大類常量闻鉴,字面量和符號引用。
結(jié)構(gòu):容量計數(shù)器(u2類型) + 常量
容量計數(shù)從1開始茂洒,目的是滿足某些常量池的索引值的數(shù)據(jù)在特定情況下需要表達“不引用任何一個常量池”的含義孟岛。
字面量(Literal): 類似Java中的常量,如文本字符串督勺,聲明為final的常量值等渠羞。
符號引用(Symbolic References):包括類和接口的全限定名(Full Qualified Name),字段的名稱和描述符(Descriptor),方法的名稱和描述符這三類常量智哀。
常量池中的表
每一項常量都是一個表次询。到JDK1.7為止,共有14種常量表類型瓷叫,表結(jié)構(gòu)見文章末尾(常量池中的14種常量項的結(jié)構(gòu)總表)屯吊。
所有常量表開始第一位為u1類型的標志位送巡,標識常量類型。
以下是常量池的項目類型表
類型 | 標志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8編碼的字符串 |
CONSTANT_Integer_info | 3 | 整形字面量 |
CONSTANT_Float_info | 4 | 浮點型字面量 |
CONSTANT_Long_info | 5 | 長整形字面量 |
CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
CONSTANT_Class_info | 7 | 類或接口的符號引用 |
CONSTANT_String_info | 8 | 字符串類型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符號引用 |
CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符號引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法類型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個動態(tài)方法調(diào)用點 |
每一種常量類型有著自己的結(jié)構(gòu)盒卸,下面以CONSTANT_Class_info類型為例骗爆,它的結(jié)構(gòu)如下表:
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | name_index | 1 |
上表中的tag用來區(qū)分常量類型,name_index是一個索引值蔽介,它指向常量池中一個CONSTANT_Utf8_info類型常量摘投,代表了這個類(或者接口)的權(quán)限定名。
訪問標志
常量池之后兩個字節(jié)標識類的訪問標志虹蓄,用于識別一些類或者接口層次的訪問信息犀呼。
具體標志位及標志含義見下表
標志名稱 | 標志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為public類型 |
ACC_FINAL | 0x0010 | 是否被聲明為final,只有類可設置 |
ACC_SUPER | 0x0020 | 是否允許使用invokespecial字節(jié)碼指令的新語言武花,invokespecial指令的語意在JDK1.0.2發(fā)生過改變圆凰,為了區(qū)別這條指令使用哪種語意杈帐,JDK1.0.2之后編譯出來的類的這個標志都必須為真 |
ACC_INTERFACE | 0x0200 | 標識這是一個接口 |
ACC_ABSTRACT | 0x0400 | 是否為abstract類型体箕,對于接口或者抽象類來說,此標志值為真挑童,其他類值為假 |
ACC_SYNTHETIC | 0x1000 | 標識這個類并非由用戶代碼產(chǎn)生的 |
ACC_ANNOTATION | 0x2000 | 標識這是一個注解 |
ACC_ENUM | 0x4000 | 標識這是一個枚舉 |
類索引累铅、父類索引、接口索引
類索引站叼、父類索引
類索引娃兽、父類索引都是一個u2類型的數(shù)據(jù)。它們會對應到常量池中的類描述符常量尽楔,通過常量中的索引值就可以找到類的全限定名字符串投储。
接口索引
接口索引集合是一組u2類型的數(shù)據(jù)的集合。第一項u2類型的數(shù)據(jù)為接口計數(shù)器阔馋,表示接口索引表的容量玛荞,如果該類沒有實現(xiàn)任何接口,該計算器值為0呕寝。
Class文件中由這三項數(shù)據(jù)來確定類的繼承關(guān)系勋眯。
字段表集合
字段表用于描述接口或者類中聲明的變量,包括類級變量以及實例級變量下梢,但不包括在方法內(nèi)部聲明的局部變量客蹋。
字段表使用標志位表示修飾符,引用常量池中的常量描述字段名及字段數(shù)據(jù)類型孽江。
字段表集合中不會列出從超類或者父接口中繼承而來的字段讶坯,但可能列出原本Java代碼之中不存在的字段,譬如岗屏,在內(nèi)部類中為了保持對外部類的訪問性辆琅,會自動添加指向外部類實例的字段钧舌。
Java語言中字段是無法重載的,必須使用不同的名稱涎跨,但是對于字節(jié)碼來說洼冻,字段可以重名,只要字段的描述符不一致
字段結(jié)構(gòu)表
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
access_flags表示字段修飾符隅很,與類的access_flags類似撞牢,并且都是一個u2的數(shù)據(jù)類型。
標志位及含義見下表
標志名稱 | 標志值 | 含義 |
---|---|---|
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_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由編譯器自動產(chǎn)生的 |
ACC_ENUM | 0x4000 | 字段是否enum |
name_index和descriptor_index都是對常量池的引用叔营。
name_index代表字段的簡單名稱屋彪。
descriptor_index代表字段和方法的描述符。
全限定名
將類全名中的“.”替換成“/”绒尊,并在最后添加一個“;”,表示全限定名結(jié)束
簡單名稱
沒有類型和參數(shù)的方法或者字段名稱
描述符
描述字段的數(shù)據(jù)類型畜挥、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值
描述符規(guī)則
基本數(shù)據(jù)類型(byte婴谱、char蟹但、double、float谭羔、int华糖、long、short瘟裸、boolean)以及void都用一個大寫字符來表示
對象類型用字符L加對象的全限定名來表示
數(shù)組類型客叉,每一緯度使用一個前置的“[”字符來描述,如定義為"java.lang.String[][]"话告,將被表示為"[[Ljava/lang/String"兼搏,一個整形數(shù)組"int[]"將被表示為"[I"
-
描述方法時,參數(shù)列表在前沙郭,返回值在后佛呻,且參數(shù)列表需要按順序放在一組小括號之內(nèi)
?
方法表集合
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用了完全一致的方式,只是在訪問標志和屬性表集合的可選項中有所區(qū)別棠绘。
方法結(jié)構(gòu)見下表
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
方法表的訪問標志中沒有ACC_VOLATILE 和 ACC_TRANSIENT 標志件相,增加了 ACC_SYNCHRONIZED、ACC_NATIVE氧苍、ACC_STRICTFP夜矗、ACC_ABSTRACT 標志。
方法訪問標志
標志名稱 | 標志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否為public |
ACC_PRIVATE | 0x0002 | 方法是否為private |
ACC_PROTECTED | 0x0004 | 方法是否為protected |
ACC_STATIC | 0x0008 | 方法是否為static |
ACC_FINAL | 0x0010 | 方法是否為final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否為synchronized |
ACC_BRIDGE | 0x0040 | 方法是否由編譯器產(chǎn)生的橋接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定參數(shù) |
ACC_NATIVE | 0x0100 | 方法是否為native |
ACC_ABSTRACT | 0x0400 | 方法是否為abstract |
ACC_STRICTFP | 0x0800 | 方法是否為strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否是由編譯器自動產(chǎn)生的 |
如果父類方法在子類中沒有被重寫让虐,方法表集合中就不會出現(xiàn)來自父類的方法信息紊撕,但有可能出現(xiàn)由編譯器自動添加的方法,如類構(gòu)造器
<clinit>
方法和實例構(gòu)造器<init>
方法
屬性表集合
Class文件赡突、字段表对扶、方法表都可以有自己的屬性表集合区赵,用于描述某些場景的專有信息。屬性表集合的限制更寬松一些浪南,不要求各個屬性表具有嚴格順序笼才,并且只要不與已有屬性名重復即可。
Code屬性
用來存儲Java程序方法體中的代碼經(jīng)過編譯處理后生成的字節(jié)碼指令络凿。每個指令是一個u1類型的單字節(jié)骡送,共可以表達256條指令。
類型 | 名稱 | 數(shù)量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 指向常量池中一個CONSTANT_Utf8_info類型的常量絮记,來表示屬性名稱 |
u4 | attribute_length | 1 | 屬性值長度 |
u2 | max_stack | 1 | 表示操作棧深度的最大值 |
u2 | max_locals | 1 | 表示局部變量表所需的存儲空間 |
u4 | code_length | 1 | 表示代碼字節(jié)碼長度 |
u1 | code | code_length | 用來存儲字節(jié)碼指令的一系列字節(jié)流 |
u2 | exception_table_length | 1 | 異常表長度 |
exception_info | exception_table | exception_table_length | Java代碼的一部分摔踱,用來實現(xiàn)Java異常及finally處理機制(而不是簡單的跳轉(zhuǎn)命令) |
u2 | attributes_count | 1 | Code屬性總數(shù) |
attribute_info | attributes | attributes_count | Code屬性 |
code_length類型為u4,理論上最大可以達到2^32-1怨愤,但虛擬機規(guī)定一個方法不能超過65535條字節(jié)碼指令派敷,否則Javac編譯器會拒絕編譯。
Slot是虛擬機為局部變量分配內(nèi)存所只用的最小單位撰洗,Javac編譯器會根據(jù)變量的作用域來分配Slot給各個變量使用篮愉。
Exception屬性
用于列舉方法中可能拋出的受查異常(throws 關(guān)鍵字后面列舉的異常)。
LineNumberTable屬性
用于描述Java源代碼行號與字節(jié)碼行號之間的對應關(guān)系了赵。
可以在Javac中分別使用-g:none或-g:lines選項來取消或要求生成這項信息潜支,如果選擇不生成,在程序運行拋出異常時柿汛,堆棧中將不會顯示出錯的行號,在調(diào)試程序時也無法按照源碼行設置斷點
LocalVariableTable屬性
用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系埠对。
可以在Javac中分別使用-g:none或-g:vars選項來取消或要求生成這項信息,如果沒有生成該屬性络断,對程序運行沒有影響,只是對代碼編寫帶來較大不便项玛,而且在調(diào)試期間無法根據(jù)參數(shù)名稱從上下文中獲得參數(shù)值
SourceFile屬性
該屬性是一個定長屬性貌笨,用于記錄生成這個Class文件的源碼文件名稱。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | 1 |
可以使用Javac的-g:none或-g:source選項來關(guān)閉或要求生成這項信息襟沮,如果不生成這項屬性锥惋,當拋出異常時堆棧中將不會顯示出錯誤代碼所屬的文件名
ConstantValue屬性
該屬性為一個定長屬性,用來通知虛擬機自動為靜態(tài)變量賦值开伏。
InnnerClass屬性
用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)膀跌。
Deprecated及Synthetic屬性
兩者為標志類型的布爾屬性,只存在是和否的區(qū)別固灵,沒有值的概念捅伤。
Deprecated屬性表示某個類、字段或方法不在被推薦使用
Synthetic屬性代表此字段或者方法并不是由Java源碼直接產(chǎn)生的巫玻,而是由編譯器自行添加的丛忆,比如Bridge Method
StackMapTable屬性
Code屬性中最多只能有一個StackMapTable屬性祠汇,否則將拋出ClassFormatError異常
Signature屬性
該屬性是在JDK1.5發(fā)布后添加到Class文件規(guī)范的一個可選定長屬性,可以出現(xiàn)在類熄诡、屬性表和方法表的屬性表中可很。
BootstrapMethods屬性
該屬性是在JDK1.7發(fā)布后增加到Class文件規(guī)范之中的一個復雜的變長屬性,用于保存invokedynamic指令引用的引導方法限定符凰浮。
常量池中的14種常量項的結(jié)構(gòu)總表
常量 | 項目 | 類型 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 | 值為1 |
length | u2 | UTF-8編碼字符串占用的字節(jié)數(shù) | |
bytes | u1 | 長度為length的UTF-8編碼的字符串 | |
CONSTANT_Integer_info | tag | u1 | 值為3 |
bytes | u4 | 按照高位在前存儲的int值 | |
CONSTANT_Float_info | tag | u1 | 值為4 |
bytes | u4 | 按照高位在前存儲的float值 | |
CONSTANT_Long_info | tag | u1 | 值為5 |
bytes | u8 | 按照高位在前存儲的long值 | |
CONSTANT_Double_info | tag | u1 | 值為6 |
bytes | u8 | 按照高位在前存儲的double值 | |
CONSTANT_Class_info | tag | u1 | 值為7 |
index | u2 | 指向全限定名常量項的索引 | |
CONSTANT_String_info | tag | u1 | 值為8 |
index | u2 | 指向字符串字面量的索引 | |
CONSTANT_Fieldref_info | tag | u1 | 值為9 |
index | u2 | 指向聲明字段的類或者接口描述符CONSTANT_Class_info的索引項 | |
index | u2 | 指向字段描述符CONSTANT_NameAndType的索引項 | |
CONSTANT_Methodref_info | tag | u1 | 值為10 |
index | u2 | 指向聲明方法的類描述符CONSTANT_Class_info的索引項 | |
index | u2 | 指向名稱及類型描述符CONSTANT_NameAndType的索引項 | |
CONSTANT_Interface_Methodref_info | tag | u1 | 值為11 |
index | u2 | 指向聲明方法的接口描述符CONSTANT_Class_info的索引項 | |
index | u2 | 指向名稱及類描述符CONSTANT_NameAndType的索引項 | |
CONSTANT_NameAndType_info | tag | u1 | 值為12 |
index | u2 | 指向該字段或方法名稱常量的索引 | |
index | u2 | 指向該字段或方法描述符常量項的索引 | |
CONSTANT_MethodHandle_info | tag | u1 | 值為15 |
reference_kind | u1 | 值必須在1-9之間(包括1和9)根穷,它決定了方法句柄的類型。方法句柄類型的值表示方法句柄的字節(jié)碼行為 | |
reference_index | u2 | 值必須是對常量池的有效索引 | |
CONSTANT_MethodType_info | tag | u1 | 值為16 |
descriptor_index | u2 | 值必須是對常量池的有效索引导坟,常量池在該索引處的項必須是CONSTANT_Utf8_info結(jié)構(gòu)屿良,表示方法的描述符 | |
CONSTANT_InvokeDynamic_info | tag | u1 | 值為18 |
bootstrap_method_attr_index | u2 | 值必須是對當前Class文件中引導方法表的bootstrap_methods[]數(shù)組的有效索引 | |
name_and_type_index | u2 | 值必須是對當前常量池的有效索引,常量池在該索引出的項必須是CONSTANT_NameAndType_info結(jié)構(gòu)惫周,表示方法名和方法描述符 |
虛擬機規(guī)范預定義的屬性
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
ConstantValue | 字段表 | final關(guān)鍵字定義的常量值 |
Deprecated | 類尘惧、方法表、字段表 | 被聲明為deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類為局部類或者匿名類時才能擁有這個屬性递递,這個屬性用于標識這個類所在的外圍方法 |
InnerClasses | 類文件 | 內(nèi)部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節(jié)碼指令的對應關(guān)系 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性喷橙,供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操作數(shù)棧所需要的類型是否匹配 |
Signature | 類、方法表登舞、字段表 | JDK1.5中新增的屬性贰逾,這個屬性用于支持泛型情況下的方法簽名,在Java語言中菠秒,任何類疙剑、接口,初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Type)践叠,則Signature 屬性會為它記錄泛型簽名信息言缤。由于Java的泛型采用擦除法實現(xiàn),在為了避免類型信息被擦除后導致簽名混亂禁灼,需要這個屬性記錄泛型中的相關(guān)信息 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | JDK1.6中新增的屬性管挟,SourceDebugExtension屬性用于存儲額外的調(diào)試信息。譬如在進行JSP文件調(diào)試時弄捕,無法通過Java堆棧來定位到JSP文件的行號僻孝,JSR-45規(guī)范為這些非Java語言編寫,卻需要編譯成字節(jié)碼并運行在Java虛擬機中的程序提供了一個進行調(diào)試的標準機制守谓,使用SourceDebugExtension屬性就可以用于存儲這個標準所新加入的調(diào)試信息 |
Synthetic | 類穿铆、方法表、字段表 | 標識方法或字段為編譯器自動生成的 |
LocalvariableTypeTable | 類 | JDK1.5中新增的屬性分飞,它使用特征簽名代替描述符悴务,是為了引入泛型語法之后能描述泛型參數(shù)化類型而添加 |
RuntimeVisibleAnnotations | 類、方法表、字段表 | JDK1.5中新增的屬性讯檐,為動態(tài)注解提供支持羡疗。RuntimeVisibleAnnotations屬性用于指明哪些注解是運行時(實際上運行時就是進行反射調(diào)用)可見的 |
RuntimeInvisibleAnnotations | 類、方法表别洪、字段表 | JDK1.5中新增的屬性叨恨,作用與RuntimeVisibleAnnotations屬性作用剛好相反,用于指明那些注解是運行時不可見的 |
RuntimeVisibeParameterAnnotations | 方法表 | JDK1.5中新增的屬性挖垛,作用與RuntimeVisibleAnnotations屬性類似痒钝,只不過作用對象為方法參數(shù) |
RuntimeInvisibeParameterAnnotations | 方法表 | JDK1.5中新增的屬性,作用與RuntimeInvisibleAnnotations屬性類似痢毒,只不過作用對象為方法參數(shù) |
AnnotationDefault | 方法表 | JDK1.5中新增的屬性送矩,用于記錄注解類元素的默認值 |
BootstrapMethods | 類文件 | JDK1.7中新增的屬性,用于保存invokedynamic指令引用的引導方法限定符 |
簡書Markdown對表格的支持不是很好哪替,點擊閱讀個人站點