?
class文件分析
以最简单的helloworld入手进行分析:
java代码:
javac编译,使用文本编辑器打开.class文件
参照:java虚拟机规范:https://docs.oracle.com/javase/specs/jvms/se8/html/ 第四节
class文件结构为:
u2 u4 分别代表2个字节、4个字节
开始对class文件进行分析,class文件是16进制的。
我们先javap拿到常量池中的内容:
1-4字节:u4: 表示魔数:cafe babe 据说是java撰写者对咖啡的喜爱命名的,如果修改魔术为其他值会报错
5-6字节:表示class文件次要版本:
7-8字节:class主版本号:
可以看出,上述的class是基于jdk1.8的
9-10字节:表示常量池的数量,这里是001d 换算成10进制即为29
后面cp_info 数量为 constant_pool[constant_pool_count-1],所以常量池真正为29-1=28个
接下来是cp_info即常量池的内容,常量池的表示通常是一个字节表示类型,根据每个类型有后续的描述,对照为:
Constant Type | Value |
---|---|
CONSTANT_Class |
7 |
CONSTANT_Fieldref |
9 |
CONSTANT_Methodref |
10 |
CONSTANT_InterfaceMethodref |
11 |
CONSTANT_String |
8 |
CONSTANT_Integer |
3 |
CONSTANT_Float |
4 |
CONSTANT_Long |
5 |
CONSTANT_Double |
6 |
CONSTANT_NameAndType |
12 |
CONSTANT_Utf8 |
1 |
CONSTANT_MethodHandle |
15 |
CONSTANT_MethodType |
16 |
CONSTANT_InvokeDynamic |
18 |
所以:
第1个常量:11字节为:0a 表示为10进制为10,其为:CONSTANT_Methodref 方法引用类型的常量
CONSTANT_Methodref_info { u1 tag; u2 class_index; 指向声明方法的类苗舒服Constant_Class_info的索引项 u2 name_and_type_index; 指向名称及类型描述符Constant——nameandtype的索引项 }
在这里我们取11-15字节内容:0a00 0600 0f : 0a:类型 00 06 表示指向常量池第六个常量所表示的信息,对照常量池,第六个是java/lang/Object , 00 0f表示指向常量池第 15 个常量所表示的信息,对照得:"<init>":()V 组合起来就是 java/lang/Object.<init>:V 即Object的init初始化方法。
继续分析后面的内容
第2个常量:16字节 09 对应CONSTANT_Fieldref
CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
拿出16-20字节:09 0010 0011 其中0010 为常量池第16个常量 “java/lang/System” ,0011为第17个:“out:Ljava/io/PrintStream;”,即“java/lang/System.out:Ljava/io/PrintStream;”
第3个常量:21字节08 为CONSTANT_String
CONSTANT_String_info { u1 tag; u2 string_index; }
拿出21-23字节:0800 12 其中00 12为第18个常量:Hello World.
第4个常量:24字节0a 为11 :CONSTANT_InterfaceMethodref
CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
拿出24-28 :0a 0013 0014 其中00 13为第19个常量:“java/io/PrintStream”,0014为第20个:“println:(Ljava/lang/String;)V”,即“java/io/PrintStream.println:(Ljava/lang/String;)V”;
第5个常量:29字节07:CONSTANT_Class
CONSTANT_Class_info { u1 tag; u2 name_index; }
拿出29-31字节:0700 15 其中00 15 为第21常量:ClassTest
第6个常量:30字节:07 :CONSTANT_Class
拿出31-33字节: 07 0016 其中0016 为第22常量:java/lang/Object
第7个常量:34字节:01:CONSTANT_Utf8 字符串常量
CONSTANT_Utf8_info { u1 tag; u2 length; Utf-8编码的字符串占用的字节数 u1 bytes[length]; 长度为length的utf-8编码的字符串 }
拿出34-37 :0100 063c 696e 6974 3e其中00 06表示长度为6个字节,紧跟的3c 696e 6974 3e为字符串的值。在class文件中字符串是使用ASCII 码进行编码的,将这些16进制字符进行转换成对应的ASCII 码后值为:“<init>”
第8个常量:01 0003 2829 56 长度3字节,转ASCII码后:“()V”
第9个常量:01 0004 436f 6465 长度4字节,转码后:“Code”
第10个常量:0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 长度15,转码后:“LineNumberTable”
第11个常量:0100 046d 6169 6e 长度4,转码后:“main”
第12个常量:01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 长度22,转码后:“([Ljava/lang/String;)V”
第13个常量:0100 0a53 6f75 7263 6546 696c 65 长度10,转码后“SourceFile”
第14个常量:01 000e 436c 6173 7354 6573 742e 6a61 7661 长度14,转码后:“ClassTest.java”
第15个常量:0c00 0700 08 :CONSTANT_NameAndType
CONSTANT_NameAndType_info { u1 tag; u2 name_index; 字段或方法名称常量的索引 u2 descriptor_index;字段或方法描述符常量项的索引 }
0c:方法引用类型 ,00 07:第七个常量“<init>”,00 08:第八个:“()V”,所以信息是:“"<init>":()V”
第16个常量:07 0017:类信息类型,第23个常量:“java/lang/System”
第17个常量:0c00 1800 19 :方法引用类型,常量池第24个常量:“out”, 第25个:“Ljava/io/PrintStream;”,所以是:“out:Ljava/io/PrintStream;”
第18个常量:01 000c 4865 6c6c 6f20 576f 726c 642e 字符串常量引用“Hello World.”
第19个常量:0700 1a:类信息类型,第26个常量:“java/io/PrintStream”
第20个常量:0c 001b 001c:方法引用类型,001b:第27个常量:“println”,001c:第28个常量:“(Ljava/lang/String;)V” ,所以是:“println:(Ljava/lang/String;)V”
第21个常量:0100 0943 6c61 7373 5465 7374:字符串引用类型:“ClassTest”
第22个常量:0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 74 字符串:“java/lang/Object”
第23个常量:01 0010 6a61 7661 2f6c 616e 672f 5379 7374 656d 字符串:“java/lang/System”
第24个常量:0100 036f 7574 字符串:“out”
第25个常量:0100 154c 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d3b 字符串:“Ljava/io/PrintStream;”
第26个常量:0100 136a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 字符串:“java/io/PrintStream”
第27个常量:0100 0770 7269 6e74 6c6e 字符串:“println”
第28个常量:0100 1528 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 字符串:“(Ljava/lang/String;)V”
以上常量分析完毕,和javap对比,结果是一致的。
接下来是常量池之后的内容
u2 access_flags访问标记:0021 :
access_flags说明:
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE |
0x0002 | Declared private ; accessible only within the defining class. |
ACC_PROTECTED |
0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC |
0x0008 | Declared static . |
ACC_FINAL |
0x0010 | Declared final ; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED |
0x0020 | Declared synchronized ; invocation is wrapped by a monitor use. |
ACC_BRIDGE |
0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS |
0x0080 | Declared with variable number of arguments. |
ACC_NATIVE |
0x0100 | Declared native ; implemented in a language other than Java. |
ACC_ABSTRACT |
0x0400 | Declared abstract ; no implementation is provided. |
ACC_STRICT |
0x0800 | Declared strictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC |
0x1000 | Declared synthetic; not present in the source code. |
这里标志值是多个值进行或运算的结果,0021由0001和0020或运算的到,也就是访问标志为public且允许使用invokespecial 字节码指令的新语义。
在访问标记后,则是类索引、父类索引、接口索引的数据,这里数据为:00 05 00 06 00 00。
类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class 文件中由这三项数据来确定这个类的继承关系。
类索引。类索引用于确定这个类的全限定名,它用一个 u2 类型的数据表示。这里的类索引是 00 05 表示其指向了常量池中第 5 个常量,通过我们之前的分析,我们知道第 5 个常量其最终的信息是 ClassTest 类。
父类索引。父类索引用于确定这个类的父类的全限定名,父类索引用一个u2类型的数据表示。这里的父类索引是 00 06 表示其指向了常量池中第 6 个常量,通过我们之前的分析,我们知道第 6 个常量其最终的信息是 Object 类。因为其并没有继承任何类,所以 ClassTest 类的父类就是默认的 Object 类。
接口索引。接口索引集合就用来描述哪个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身就是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。对于接口索引集合,入口第一项是 u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量,而在接口计数器后则紧跟着所有的接口信息。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
字段表集合
field_count 、field_info
字段表集合用于描述接口或者类中声明的变量,这里的数据为:00 00。
这里说的字段包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。在类接口集合后的2个字节是一个字段计数器,表示总有有几个属性字段。在字段计数器后,才是具体的属性数据。
字段表的每个字段用一个名为 field_info 的表来表示,field_info 表的数据结构如下所示:
因为我们并没有声明任何的类成员变量或类变量,所以在 字节码文件中,字段计数器为 00 00,表示没有属性字段。
方法表集合
methods_count、methods
方法结构:
ClassTest类的字节码文件中,
方法计数 0002 有两个方法,
第一个方法:0001 0007 0008 0001 0009 0000 001d 0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0a00 0000 0600 0100 0000 01
其中0001 表示ACC_PUBLIC,即访问标识为public,之后的两个字节0007是放方法名称的索引,执行了第七个常量为:“<init>”,接着的两个字节是方法描述符索引项,0008,表示常量池第八个:“()V”, 0001释放属性计数器,表示该方法的属性表有一个属性,属性表:
前两个是名字索引、之后的4个字节是属性长度、接着是属性的值。这里为0009,第九个常量为:“Code”,说明此属性是方法的字节码描述,Code属性的表结构为:
TODO
?