本篇文章将通过一段java代码和它的class文件来了解类文件结构。
什么是class文件?
class文件是一组以8位字节为基础的二进制数据流。
示例代码 Main.java:
1 package javaapplication1; 2 3 /** 4 * 5 * @author yirain 6 */ 7 public class TestClass { 8 private int m; 9 10 public int inc(){ 11 return m + 1; 12 } 13 14 }
用16进制编辑器打开Main.class文件如下(图1):
看上图的左上角有一个offset,相当于坐标原点,为了避免重复贴图占用太多篇幅,以下数据项的定位都按offset来表述。例如第一个数据项CA,坐标位(000000)。
魔数和Class文件的版本
offset(000000到000003) 为魔数CAFEBABE(咖啡宝贝),作用为确定这个文件是否为一个能被虚拟机接受的class文件。
offset(000004到000007),表示版本号位49.0,表示该文件可以被JDK1.6及以上版本的虚拟机执行。
常量池
接下来是常量池入口。
表1 常量池的项目类型
常量表类型 | 标志值(占1 byte) | 描述 |
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int类型的字面值 |
CONSTANT_Float | 4 | float类型的字面值 |
CONSTANT_Long | 5 | long类型的字面值 |
CONSTANT_Double | 6 | double类型的字面值 |
CONSTANT_Class | 7 | 对一个类或接口的符号引用 |
CONSTANT_String | 8 | String类型字面值的引用 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类中方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 对一个接口中方法的符号引用 |
CONSTANT_NameAndType | 12 | 对一个字段或方法的部分符号引用 |
offset(000008到000009) 0x0016(1*16+6-1)为常量池容量计数值,这里是21,也就是说该常量池有21个数据项。
第一个常量:接下来offset(00000A)0x0A,查询常量池项目类型表可知为类中方法的符号引用。
offset(00000B到00000C)0x0004为一个索引值(给出包含所属类名的CONSTANT_Utf8表的索引),表示指向常量池的第四个常量。
offset(00000D到00000E)0x0012为一个索引值(包含字段名或方法名以及描述符的 CONSTANT_NameAndType表 的索引),表示指向常量池的第18个常量。
第二个常量:offset(00000F)0x09, 查询常量池项目类型表可知为对一个字段的符号引用。
offset(000010到000011)0x0003为一个索引值,表示指向常量池的第3个常量。
offset(000012到000013)0x0013为一个索引值,表示指向常量池的第19个常量。
第三个常量:offset(000014)0x07, 查询常量池项目类型表可知为对一个类或接口的符号引用。
offset(000015到000016)0x0014为一个索引值,表示指向常量池的第20个常量。
第四个常量:offset(000017)0x07, 查询常量池项目类型表可知为对一个类或接口的符号引用。
offset(000018到000019)0x0014为一个索引值,表示指向常量池的第21个常量。
第五个常量:offset(00001A)0x01, 查询常量池项目类型表可知为UTF-8编码的Unicode字符串。
offset(00001B到00001C)0x0001为该常量的长度为一个字节,后面紧跟着的)0x6D为该字符串,换算结果是m。
后面的数据项用javap来分析:
由常量表也可验证上面分析是正确的。
访问标志
在常量池结束后,后面两个字节为访问标志,用于识别一些类和接口的访问信息,在该例子中,ACC_PUBLIC为真,ACC_SUPER为真,所以offset(000221到000222)为0x0021.
类索引、父类索引和接口索引集合
类索引和父类索引都是u2类型的数据。也就说从偏移地址 000223开始的0x0003, 0x0004,0x0000,查询常量池找出对应的类和父类的常量。
字段表集合
字段表用于描述接口或类中声明的变量。也就说从偏移地址 000229开始0x0001, 表明该字段表只有一个字段表数据。接下来是access_flags为0x0002 , 表明为private。 name_index为0x0005, 表明指向第五个常量m。 接下来为descriptor_index是0x0006,查找常量表可知为I。则语义为
private int m
方法表集合
从偏移地址000251,表明该方法表有两个方法表数据。接下来是access_flags为0x0001 ,表明为public。 name_index为0x0007, 表明指向第7个常量<init>。接下来为descriptor_index是0x0008,查找常量表可知为()v。属性表计数器attitudes_count为0x0001, 表明有一项属性,索引值为0x0009:code。