class文件是指以.class為文件后綴的java虛擬機(jī)可裝載文件。無論該class文件是在linux上進(jìn)行編譯的,還是在windows環(huán)境下編譯的,無論虛擬機(jī)是在何種平臺(tái)下實(shí)現(xiàn)和運(yùn)行的,class文件使得Java虛擬機(jī)可以正確的讀取、解釋所有的class文件。 在分析和研究class文件之前,先提出有一些問題:
1.類/接口(class文件也可能定義的是接口,所以還是不要理解為類文件為好)內(nèi)有哪些內(nèi)容?
2.以上內(nèi)容分別保存在class文件的什么地方?
3.這些內(nèi)容在加載過程中又如何被讀取和解析?
4.這些內(nèi)容加載后又會(huì)被解析成為什么樣的數(shù)據(jù)結(jié)構(gòu)保存在虛擬機(jī)中?
5.這些數(shù)據(jù)結(jié)構(gòu)在虛擬機(jī)的運(yùn)行過程中又是如何被使用的?
擴(kuò)展問題:
6.如何防止class文件被劫持?
7.如何防止class文件被反編譯?
class文件的組織結(jié)構(gòu)定義如下:
ClassFile{magic u4,minor_version u2,major_version u2,constant_pool_count u2,constant_pool cp_info*constant_pool_count,access_flags u2,this_class u2,super_class u2,interface_count u2,interfaces u2 * interface_count,fields_count u2,fields field_info * fields_count,methods_count u2,methods method_info * methods_count,attributes_count u2,attributes attributes_info * attributes_count}
以如下程序?yàn)槔瑢?duì)生成的class文件進(jìn)行分析:
1 //TestInterface.java 2 public interface TestInterface { 3 public void interface_method(); 4 } 5 6 //TestClass.java 7 public class TestClass implements TestInterface{ 8 PRivate int private_global = 3; 9 public int public_global;10 private static final int sfi = 127;11 public static final String sfs = "test strings";12 private StringBuilder sb;13 14 public void method_1(){15 private_global = public_global * 2;16 sb.append(private_global);17 }18 19 public void method_2(int pub){20 public_global = pub;21 }22 23 public void method_2(int pub, boolean flag){24 int tmp = 5;25 public_global = pub * 2 + tmp;26 }27 28 @Override29 public void interface_method() {30 method_1();31 }32 33 }
1.magic(魔數(shù)) 值為0xcafebabe,沒有特別的意義,放在文件頭并選取用來標(biāo)記改文件是一個(gè)class文件。

2.minor_version/major_version(次版本號(hào)和主版本號(hào))

次版本號(hào)和主版本號(hào)分別為0x0000和0x0032(50),即主版本號(hào)位50,次版本號(hào)為0
3.constant_pool_count/constant_pool(常量池?cái)?shù)量和常量池)
常量池保存了文件中類或接口相關(guān)的一切常量,字面常量(直接量),如文字字符串、final變量值,以及符號(hào)引用,如類或接口的全限定名、方法或字段的簡(jiǎn)單名稱和描述符。
其中,全限定名用以在當(dāng)前命名空間內(nèi)唯一標(biāo)志類或接口,在java語言中如java.lang.Object,在class文件中,會(huì)將'.'用'/'取代,即表示為java/lang/Object 簡(jiǎn)單名稱就是簡(jiǎn)單的方法名或變量名的字符串,如java.lang.Object的成員方法wait()的簡(jiǎn)單名稱為"wait"。
而只有簡(jiǎn)單名稱是無法唯一確定調(diào)用的方法是哪一個(gè),由于Java語言的特性,方法可能被重寫或重載, 所以還需要根據(jù)方法的返回值、參數(shù)數(shù)量、類型、順序來確定一個(gè)方法描述符來唯一標(biāo)志該方法,字段的描述符則簡(jiǎn)單得多,只需要給出字段的類型 描述符讓我們聯(lián)想起PE/ELF文件的函數(shù)簽名,它由上下文無關(guān)語法定義:
FieldDescriptor: FieldTypeComponentType: FieldTypeFieldType: BaseType ObjectType ArrayTypeBaseType: B C D F I J S ZObjectType: L<classname>;ArrayType: [ComponentTypeMethodDescriptor: (ParameterDescriptor*) ReturnDescriptorParameterDescriptor: FieldTypeReturnDescriptor: FieldType V
其終結(jié)符號(hào)如下:

以深入java虛擬機(jī)上的示例作為參考:


下面看class文件內(nèi)常量池部分: 首先是常量池?cái)?shù):即(0x35)53個(gè)常量池

Java虛擬機(jī)將常量池組織成為列表(可以看做是一個(gè)常量池的數(shù)組)的形式,常量池內(nèi)容可能指向其他常量池,并且class文件中其他部分內(nèi)容也可能指向常量池入口,這些常量池通過該常量池在常量池列表中的索引來定位,常量池列表的0號(hào)常量池其實(shí)是空的,作為常量池的NULL引用,即常量池列表的第一項(xiàng)實(shí)際上是1號(hào)常量池,常量池列表實(shí)際上只有constant_pool_count - 1個(gè)常量池項(xiàng)。 隨后是常量池列表,常量池的結(jié)構(gòu)如下:
cp_info{tag,info}常量池的固定第一個(gè)字節(jié)是常量值標(biāo)簽,用來描述該常量池保存內(nèi)容的類型,常量池標(biāo)志和含義如下:
根據(jù)常量池標(biāo)志tag的不同,info有不同的組織方式:
(1).CONSTANT_Utf8結(jié)構(gòu):
(可以看出length由2個(gè)字節(jié)表示,最大長(zhǎng)度就應(yīng)該是65536字節(jié))
該類型是一個(gè)長(zhǎng)度可變(長(zhǎng)度為length)的常量字符串表,用來存儲(chǔ)以下類型的字符串:
字符的存放:
對(duì)于0x0001-0x007f的字符將使用一個(gè)字節(jié)(該字節(jié)的0-6位,第7位為0)存放
對(duì)于0x080-0x07ff的字符將使用兩個(gè)字節(jié)(依次高字節(jié)的0-5位和低字節(jié)的0-4位,剩余位分別為10、110)存放

對(duì)于0x0800-0xffff的字符將使用3個(gè)字節(jié)(依次為高字節(jié)的0-5中間字節(jié)的0-5,和低字節(jié)的0-3位,剩余位分別為10、10、1110)存放。

(2).CONSTANT_Integer結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u4 | bytes | 1 |
按高位在前的格式存儲(chǔ)int型數(shù)據(jù)
(3).CONSTANT_Float結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u4 | bytes | 1 |
按高位在前的格式存儲(chǔ)float型數(shù)據(jù)
(4).CONSTANT_Doube結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u8 | bytes | 1 |
按高位在前的格式存儲(chǔ)double型數(shù)據(jù)
(5).CONSTANT_Long結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u8 | bytes | 1 |
按高位在前的格式存儲(chǔ)long型數(shù)據(jù)
(6).CONSTANT_Class結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u2 | name_index | 1 |
name_index為類或者接口符號(hào)引用的CONSTANT_Utf8常量池的索引(全限定名)
(7).CONSTANT_String結(jié)構(gòu):
| Type | Name | Count |
|---|---|---|
| u2 | sring_index | 1 |
string_index為字符串的CONSTANT_Utf8常量池的索引
(8).CONSTANT_Fieldref結(jié)構(gòu):
描述了指向字段的符號(hào)引用,其內(nèi)容分兩項(xiàng)表示,一項(xiàng)為被引用字段所在類或接口的CONSTANT_Class常量池索引,一項(xiàng)為字段的簡(jiǎn)單名稱和描述符,指向一個(gè)CONSTANT_NameAndType常量池
| Type | Name | Count |
|---|---|---|
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
(9).CONSTANT_Methodref結(jié)構(gòu):
與CONSTANT_Fieldref類似,描述了指向類中聲明的方法的符號(hào)引用,其內(nèi)容分兩項(xiàng)表示,一項(xiàng)為被引用方法所在類的CONSTANT_Class常量池索引,一項(xiàng)為方法的簡(jiǎn)單名稱和描述符,指向一個(gè)CONSTANT_NameAndType常量池
| Type | Name | Count |
|---|---|---|
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
(10).CONSTANT_InterfaceMethodref結(jié)構(gòu):
與CONSTANT_Methodref類似,描述了指向接口中聲明的方法的符號(hào)引用,其內(nèi)容分兩項(xiàng)表示,一項(xiàng)為被引用方法所在接口的CONSTANT_Class常量池索引,一項(xiàng)為方法的簡(jiǎn)單名稱和描述符,指向一個(gè)CONSTANT_NameAndType常量池
| Type | Name | Count |
|---|---|---|
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
(11).CONSTANT_NameAndType結(jié)構(gòu):
可以預(yù)見,該常量池提供了所引用字段或方法的簡(jiǎn)單名稱和常量池入口
| Type | Name | Count |
|---|---|---|
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
注意區(qū)分class_index指向的是對(duì)應(yīng)類的常量池,該CONSTANT_Class常量池指向一個(gè)全限定名的CONSTANT_Utf8字符串常量池
常量池部分的解析可以參考http://note.youdao.com/share/?id=3c1f3fac45837f95cc87fa6694a25b84&type=note
4.access_flags
該項(xiàng)2字節(jié)標(biāo)志了所定義類或接口的類型信息
該文件中access_flags為0x0021 ,可見該類是public super類型。

5.this_class(當(dāng)前類)
該項(xiàng)2字節(jié)標(biāo)志了所定義類或接口的CONSTANT_Class常量池索引,最終指向全限定名”TestClass”

6.super_class(超類)
該項(xiàng)2字節(jié)標(biāo)志了所定義類的超類的CONSTANT_Class常量池索引,最終指向全限定名”java/lang/Object”

7.interfaces_count/interfaces(接口數(shù)和接口)
首先2字節(jié)是在該類中直接實(shí)現(xiàn)或擴(kuò)展的接口數(shù),后面緊隨若干個(gè)(接口數(shù))2字節(jié),代表所直接實(shí)現(xiàn)或擴(kuò)展的接口的CONSTANT_Class常量池的索引 
這里只實(shí)現(xiàn)了一個(gè)接口,就是5號(hào)常量池,即全限定名”TestInterface”所定義的接口
8.fields_count/fields(字段數(shù)和字段)
fields_count是類變量(靜態(tài)變量)和實(shí)例變量(非靜態(tài)變量)的字段數(shù)總和,與constant_pool組織形式類似,后面是fields_count個(gè)field_info,需要注意的是,當(dāng)前類的字段不會(huì)包含其超類或父接口中繼承的字段,也會(huì)包含在Java源文件中沒有但是在編譯時(shí)添加的一些字段。field_info結(jié)構(gòu)如下:
field_info{access_flags u2,name_index u2,descriptor_index u2,attributes_count u2,attributes attributes_info * attributes_cout }
(1).字段的accesss_flags與描述當(dāng)前類的access_flags不同:
類中聲明的字段,只能擁有ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個(gè)標(biāo)志中的一個(gè)。ACC_FINAL
和ACC_VOLATILE 不能同時(shí)設(shè)置。所有接口中聲明的字段必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL 這三種標(biāo)志。
(2).name_index為該字段的簡(jiǎn)單名稱的CONSTANT_Utf8常量池索引
(3).descriptor_index為該字段的描述符的CONSTANT_Utf8常量池索引
(4).attributes_count和attributes是attributes_count個(gè)attribute_info結(jié)構(gòu)所表述的屬性集合。在字段域出現(xiàn)的屬性有ConstantValue(final常量)、Deprecated(被禁用的指示符)、Synthetic(編譯器產(chǎn)生的指示符)
屬性出現(xiàn)在ClassFile、field_info、method_info、Code_attribute中。所有Java虛擬機(jī)必須能夠識(shí)別Code、ConstantValue、Exception。對(duì)于能夠正常實(shí)現(xiàn)Java/Java2平臺(tái)類庫的虛擬機(jī)必須能夠識(shí)別InnerClass和Synthetic屬性。

attribute_info的結(jié)構(gòu)如下:
attribute_info{attribute_name_index u2,attribute_length u4,info u1,}attribute_name_index為描述屬性的字符串名稱(即上述列出屬性名)的CONSTANT_Utf8常量池索引,
attribute_length為后面屬性內(nèi)容的長(zhǎng)度
這里先介紹將字段可能用到的ConstantValue、Deprecated和Synthetic屬性
(1).ConstantValue
| Type | Name | Count |
|---|---|---|
| u2 | constantvalue_index | 1 |
該屬性用于描述值為常量的字段,并且在包含該屬性的字段其access_flag必須為ACC_STATIC,以表明這是一個(gè)靜態(tài)常量。
constantvalue_index指向提供常量值的常量池索引(此外,ConstantValue對(duì)應(yīng)的屬性的attribute_length始終為2)
(2).Deprecated
被@Deprecated所注釋的字段、方法或類型,表示雖然該字段、方法或類型仍然存在,但是不建議使用,其在未來的版本中可能會(huì)被移除
Deprecated對(duì)應(yīng)的屬性的attribute_length值始終為0
(3).Synthetic
用來指明為編譯器所產(chǎn)生的字段、方法或類型
同樣,這是一個(gè)固定長(zhǎng)度屬性,其
對(duì)應(yīng)的屬性的attribute_length值始終為0
class文件field域解析:
首先由開頭兩個(gè)字節(jié)看出有5個(gè)field_info 
field1:
access_flag為ACC_PRIVATE,標(biāo)志其為private類型
name_index為0x0007,指向7號(hào)常量池,即簡(jiǎn)單名稱為”private_global”
descriptor_index為0x0008,指向8號(hào)常量池,即描述符為”I”
attributes_count為0,即沒有任何屬性 
field2:
access_flag為ACC_PUBLIC,標(biāo)志其為public類型
name_index為0x0009,指向9號(hào)常量池,即簡(jiǎn)單名稱為”public_global”
descriptor_index為0x0008,指向8號(hào)常量池,即描述符為”I”
attributes_count為0,即沒有任何屬性 
field3:
access_flag為0x0010|0x0008|0x0002,即ACC_FINAL | ACC_STATIC | ACC_PRIVATE,標(biāo)志其為private static final類型
name_index為0x000A,指向10號(hào)常量池,即簡(jiǎn)單名稱為”sfi”
descriptor_index為0x0008,指向8號(hào)常量池,即描述符為”I”
attributes_count為1,即有一個(gè)屬性 
該屬性的
attribute_name_index為0x000B,指向11號(hào)常量池,即”ConstantValue”屬性
attribute_length為2,即固定2個(gè)字節(jié)
constantvalue_index為0x000C,指向12號(hào)常量池,即sfi的值為”127”(這里還是字符串) 
field4:
access_flag為0x0010|0x0008|0x0001,即ACC_FINAL | ACC_STATIC | ACC_PUBLIC,標(biāo)志其為public static final類型
name_index為0x000D,指向13號(hào)常量池,即簡(jiǎn)單名稱為”sfs”
descriptor_index為0x000E,指向14號(hào)常量池,即描述符為”Ljava/lang/String;”
attributes_count為1,即有一個(gè)屬性 
該屬性的
attribute_name_index為0x000B,指向11號(hào)常量池,即”ConstantValue”屬性
attribute_length為2,即固定2個(gè)字節(jié)
constantvalue_index為0x000F,指向15號(hào)常量池,即sfs的值為”test strings” 
field5:
access_flag為ACC_PRIVATE,標(biāo)志其為private類型
name_index為0x0011,指向17號(hào)常量池,即簡(jiǎn)單名稱為”sb”
descriptor_index為0x0012,指向18號(hào)常量池,即描述符為”Ljava/lang/StringBuilder;”
attributes_count為0,即沒有任何屬性 
9.methods_count/methods(方法數(shù)/方法)
方法域的method_info結(jié)構(gòu)與字段域是一樣的,即
method_info{access_flags u2,name_index u2,descriptor_index u2,attributes_count u2,attributes attributes_info * attributes_cout }
不過其access_flag有些不同

如果一個(gè)方法是抽象方法,那么它就不能為private、static、final、synchronized、native和strict類型
在方法域出現(xiàn)的屬性有Code、Deprecated、Exceptions、Synthetic
下面介紹新出現(xiàn)的兩種屬性Code和Exceptions:
(1).Code
其info域的結(jié)構(gòu)如下
其中:
首先看exception_table_info的結(jié)構(gòu),可以預(yù)見,一個(gè)異常在代碼中的描述就必須包含作用域、異常類型和異常處理三部分內(nèi)容,看看exception_table_info是不是這樣組織的
exception_table_info{start_pc u2,end_pc u2,handler_pc u2,catch_type u2,}不出所料,start_pc就是異常處理器起始位置相對(duì)該段代碼的偏移量,
end_pc就是異常處理器結(jié)束位置相對(duì)該段代碼的偏移量,
handler_pc就是異常處理器第一條指令相對(duì)該段代碼的偏移量
catch_type指向描述該異常類型(java/lang/Throwable或其子類)的CONSTANT_Class常量池索引,二若catch_type為0,那么異常處理器將處理所有異常
(2).LineNumberTable
行號(hào)表與ELF/PE文件看上去有著異曲同工之妙,它同樣建立了方法的字節(jié)碼偏移量和源代碼行號(hào)之間的映射關(guān)系。其info域結(jié)構(gòu)如下
| Type | Name | Count |
|---|---|---|
| u2 | line_number_table_length | 1 |
| line_number_info | line_number_table | line_number_table_length |
line_number_table_length描述了行號(hào)表的項(xiàng)數(shù),注意,并不是行號(hào)表各項(xiàng)并不是逐行對(duì)應(yīng),而是可能按照任何順序排列,并且可能多項(xiàng)對(duì)應(yīng)同一行。
line_number_info的結(jié)構(gòu)如下:
line_number_info{start_pc u2,line_number u2,}其中,start_pc描述了該行起始第一個(gè)字節(jié)碼對(duì)應(yīng)該段代碼的偏移量,line_number描述了對(duì)應(yīng)的行號(hào)。
(3).LocalVariableTable
這里由LocalVariableTable保存了方法的棧幀中局部變量域源代碼中局部變量的名稱和描述符之間的映射關(guān)系。

同樣,局部變量表也是以local_variable_table_length個(gè)local_variable_info結(jié)構(gòu)進(jìn)行組織的
local_variable_info的結(jié)構(gòu)如下:
local_variable_info{start_pc u2,length u2,name_index u2,descriptor_index u2,index u2,}(4).Exceptions屬性
區(qū)別于描述Code屬性的exception_table部分,這里是方法可能會(huì)拋出的異常,而非包圍代碼的try/catch異常。Exceptions屬性的info域格式如下:
| Type | Name | Count |
|---|---|---|
| u2 | number_of_exceptions | 1 |
| u2 | exception_index_table | number_of_exceptions |
exception_index_table是該方法拋出的異常類型的CONSTANT_Class常量池索引,number_of_exceptions指出了拋出異常類型的數(shù)量。
methods部分的解析可以參考http://note.youdao.com/share/?id=b1c762ba1ee4874a23eb8a512cccf507&type=note
10.attributes_count/attributes(屬性數(shù)和屬性)
最后還有兩種屬性:InnerClass和SourceFile
(1).SourceFile
其info結(jié)構(gòu)為:
| Type | Name | Count |
|---|---|---|
| u2 | sourcefile_index | 1 |
給出了指向源文件名的CONSTANT_Utf8常量池索引
如該class文件最后的attributes_count為1,其
attribute_name_index為0x0033,指向51號(hào)常量池,即”SourceFile”屬性
attribute_length為0x02,即2個(gè)字節(jié)
sourcefile_index為0x0034,指向52號(hào)常量池,即源文件名為”TestClass.java”
(2).InnerClasses
| Type | Name | Count |
|---|---|---|
| u2 | number_of_classes | 1 |
| classes_info | classes | number_of_classes |
classses_info描述了內(nèi)部類(成員嵌套類、局部嵌套類和匿名嵌套類)的信息,其結(jié)構(gòu)如下:
classes_info{inner_class_info_index u2,outer_class_info_index u2,inner_name_index u2,inner_class_access_flags u2,}以如下內(nèi)容為例

其生成的class文件如下: 
其InnerClasses屬性內(nèi)容為: 
其中匿名內(nèi)部類Runnable的全限定名為InnerClassTest$1,由于其不是一個(gè)成員嵌套類(該類是局部嵌套類),其outer_class_info_index 為0,由于該類是一個(gè)匿名內(nèi)部類,其inner_name_index為0(即簡(jiǎn)單名稱為空)
局部嵌套類NestedLocalClass的全限定名為InnerClassTest$1NestedLocalClass,由于其不是一個(gè)成員嵌套類,其outer_class_info_index為0,其簡(jiǎn)單名稱為”NestedLocalClass”,access_flag為final
成員嵌套類NestedMemberClass的全限定名為InnerClassTest$NestedMemberClass,其簡(jiǎn)單名稱為”NestedMemberClass”,access_flag為public static final
此外,我們注意到內(nèi)嵌類的內(nèi)容會(huì)定義在各自的class文件中,而不會(huì)出現(xiàn)在InnerClassTest類的class文件中,在NestedMemberClass的class文件中有著如下的InnerClasses屬性: 
在subClass的class文件中也有著如下的InnerClasses屬性: 
可以看出,每個(gè)作為外圍類的內(nèi)部類的類都將保存在該外圍類的CONSTANT_Class常量池中,并有一個(gè)inner_class_info結(jié)構(gòu)加以描述
如InnerClassTest的3個(gè)內(nèi)部類項(xiàng),NestedMemberClass的第二個(gè)內(nèi)部類項(xiàng)
但是需要注意,subClass在被沒有被InnerClassTest直接引用時(shí),是不會(huì)出現(xiàn)在InnerClassTest的InnerClasses屬性中的
另外,InnerClasses還將表述內(nèi)嵌類型的外圍類,作為內(nèi)部類的所有外圍類都將保存在該內(nèi)部類的CONSTANT_Class常量池中,并有一個(gè)inner_class_info結(jié)構(gòu)加以描述
如NestedMemberClass的第1個(gè)外部類項(xiàng),subClass的2個(gè)外部類項(xiàng)
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注